4

I. Overview

Before React 16, the update process of VirtualDOM was implemented using the Stack architecture, which is a recursive way. One problem with this comparison method is that once the task starts, it cannot be interrupted. If the number of components in the application is large, the level of the Virtual DOM will be deeper. If the main thread is occupied for a long time, it will block rendering and cause lag. In order to avoid this situation, the update operation should not exceed 16ms. If it exceeds 16ms, it needs to be paused and let the browser perform the rendering operation, and then continue to perform the update calculation.

The Fiber architecture was created to support "interruptible rendering". In React, the fiber tree is a data structure that can convert the virtual dom tree into a linked list, so that it can support breakpoint restarts when performing traversal operations. The schematic diagram is as follows.

在这里插入图片描述

2. Fiber principle

Fiber can be understood as an execution unit or a data structure.

2.1 An execution unit

Fiber can be understood as an execution unit. Each time an execution unit is executed, react will check how much time is left, and if there is no time, it will give up control. The core interaction process between React Fiber and the browser is as follows:

在这里插入图片描述

It can be seen that React first requests scheduling from the browser. If the browser still has free time in a frame, it will determine whether there is a task to be executed. If it does not exist, it will directly hand the control to the browser; if it exists, it will be executed. For the corresponding task, after the execution is completed, it will be judged whether there is still time. If there is time and the task to be executed, it will continue to execute the next task, otherwise the control will be handed over to the browser for rendering.

Therefore, we can understand Fiber as an execution unit, and an execution unit must be completed at one time, without pause, and this small execution unit can transfer control to the browser to respond to the user after it is considered to be executed, thereby improving rendering efficiency.

2.2 A data structure

In the official document introduction, Fiber is explained as a data structure, the well-known linked list. Each Virtual DOM can be represented as a fiber, as shown in the figure below, each node is a fiber.

在这里插入图片描述

Usually, a fiber includes attributes such as child (the first child node), sibling (sibling node), and return (parent node). The implementation of the React Fiber mechanism depends on the above data structure.

2.3 Fiber linked list structure

The Fiber structure uses a linked list, to be precise, a singly linked list tree structure. See the ReactFiber.js source code for details. Let's take a look at the Fiber linked list structure in order to better understand the Fiber traversal process in the future.

在这里插入图片描述

Each of the above units contains payload (data) and nextUpdate (pointer to the next unit). The definition structure is as follows:

 class Update {
  constructor(payload, nextUpdate) {
    this.payload = payload          //payload 数据
    this.nextUpdate = nextUpdate    //指向下一个节点的指针
  }
}

Next, define a queue to connect each unit in series, in which two pointers are defined: the head pointer firstUpdate and the tail pointer lastUpdate, which are used to point to the first unit and the last unit, and add the baseState attribute to store the state in React state.

 class UpdateQueue {
  constructor() {
    this.baseState = null // state
    this.firstUpdate = null // 第一个更新
    this.lastUpdate = null // 最后一个更新
  }
}

Next, define two methods: insert node unit (enqueueUpdate), update queue (forceUpdate). When inserting a node unit, you need to consider whether a node already exists. If it does not exist, you can directly point firstUpdate and lastUpdate to this node. The update queue is to traverse this linked list and update the value of state according to the content of the payload

 class UpdateQueue {
  //.....
  
  enqueueUpdate(update) {
    // 当前链表是空链表
    if (!this.firstUpdate) {
      this.firstUpdate = this.lastUpdate = update
    } else {
      // 当前链表不为空
      this.lastUpdate.nextUpdate = update
      this.lastUpdate = update
    }
  }
  
  // 获取state,然后遍历这个链表,进行更新
  forceUpdate() {
    let currentState = this.baseState || {}
    let currentUpdate = this.firstUpdate
    while (currentUpdate) {
      // 判断是函数还是对象,是函数则需要执行,是对象则直接返回
      let nextState = typeof currentUpdate.payload === 'function' ? currentUpdate.payload(currentState) : currentUpdate.payload
      currentState = { ...currentState, ...nextState }
      currentUpdate = currentUpdate.nextUpdate
    }
    // 更新完成后清空链表
    this.firstUpdate = this.lastUpdate = null
    this.baseState = currentState
    return currentState
  }
}

Finally, we write a test case: instantiate a queue, add many nodes to it, and then update the queue.

 let queue = new UpdateQueue()
queue.enqueueUpdate(new Update({ name: 'www' }))
queue.enqueueUpdate(new Update({ age: 10 }))
queue.enqueueUpdate(new Update(state => ({ age: state.age + 1 })))
queue.enqueueUpdate(new Update(state => ({ age: state.age + 1 })))
queue.forceUpdate()
console.log(queue.baseState);       //输出{ name:'www',age:12 }

2.4 Fiber Node

The splitting unit of the Fiber framework is fiber (a node on the fiber tree). In fact, it is split according to the virtual DOM node. We need to generate the fiber tree according to the virtual DOM. The data structure of the Fiber node is as follows:

 {
    type: any,   //对于类组件,它指向构造函数;对于DOM元素,它指定HTML tag
    key: null | string,  //唯一标识符
    stateNode: any,  //保存对组件的类实例,DOM节点或与fiber节点关联的其他React元素类型的引用
    child: Fiber | null, //大儿子
    sibling: Fiber | null, //下一个兄弟
    return: Fiber | null, //父节点
    tag: WorkTag, //定义fiber操作的类型, 详见https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactWorkTags.js
    nextEffect: Fiber | null, //指向下一个节点的指针
    updateQueue: mixed, //用于状态更新,回调函数,DOM更新的队列
    memoizedState: any, //用于创建输出的fiber状态
    pendingProps: any, //已从React元素中的新数据更新,并且需要应用于子组件或DOM元素的props
    memoizedProps: any, //在前一次渲染期间用于创建输出的props
    // ……     
}

Finally, all fiber nodes form a tree linked list through the following attributes: child, sibling and return.

在这里插入图片描述

Other properties are memoizedState (the state of the fiber that creates the output), pendingProps (the props to be changed), memoizedProps (the props of the output created by the last rendering), pendingWorkPriority (defining the priority of the fiber work), etc. are not introduced too much. .

3. Fiber execution process

The overall execution process of Fiber can be divided into two stages: rendering and scheduling: render stage and commit stage. Among them, the render phase is interruptible and needs to find out the changes of all nodes; while the commit phase is uninterruptible and only executes all the changes.

3.1 render stage

The main task of this stage is to find out the changes of all nodes, such as node addition, deletion, attribute change, etc. These changes are collectively referred to as side effects by React. In this stage, a Fiber tree will be constructed, and tasks will be carried out with the virtual dom node as the dimension. Splitting, that is, a virtual Dom node corresponds to a task, and the final result is an effect list, from which it is counted which nodes need to be updated, which nodes need to be added, and which nodes need to be deleted.

3.1.1 Traversal process

React Fiber first converts the virtual DOM tree into a Fiber tree, so each node has child, sibling, and return attributes. The post-order traversal method is used when traversing the Fiber tree. The process of post-order traversal is as follows:
Traverse from the vertex;
If there is an eldest son, traverse the eldest son first; if there is no eldest son, it means that the traversal is complete;
The eldest son: a. If there is a younger brother, return the younger brother and skip to 2 b. If there is no younger brother, return to the parent node, and mark the completion of the parent node traversal, skip to 2 d. If there is no parent node, mark the end of the traversal

The following is a schematic diagram of post-order traversal:

在这里插入图片描述

3.1.2 Collect the effect list

The specific steps to collect the effect list are:

1. If the current node needs to be updated, tag the current node state (props, state, context, etc.);
2. Create a fiber for each child node. If no child fiber is generated, end the node, merge the effect list into return, and use the sibling node of this node as the next traversal node; otherwise, use the child node as the next traversal node;
3. If there is time left, start the next node, otherwise wait for the next time the main thread is idle before starting the next node;
4. If there is no next node, enter the pendingCommit state. At this time, the effect list is collected and ends.

The schematic diagram of the traversal sequence for collecting the effect list is as follows:

在这里插入图片描述

3.2 Commit stage

The commit phase needs to execute the side effects calculated in the previous phase and need to be processed at one time. This phase cannot be suspended, otherwise there will be discontinuous UI updates. At this stage, all updates need to be committed to the DOM tree according to the effect list.

3.2.1 Update the view according to the effect list

At this stage, the view is updated according to the effect list of a fiber. This time, only three operations of adding a node, deleting a node, and updating a node are listed:

 /**
* 根据一个 fiber 的 effect list 更新视图
*/
const commitWork = currentFiber => {
  if (!currentFiber) return
  let returnFiber = currentFiber.return
  let returnDOM = returnFiber.stateNode // 父节点元素
  if (currentFiber.effectTag === INSERT) {  // 如果当前fiber的effectTag标识位INSERT,则代表其是需要插入的节点
    returnDOM.appendChild(currentFiber.stateNode)
  } else if (currentFiber.effectTag === DELETE) {  // 如果当前fiber的effectTag标识位DELETE,则代表其是需要删除的节点
    returnDOM.removeChild(currentFiber.stateNode)
  } else if (currentFiber.effectTag === UPDATE) {  // 如果当前fiber的effectTag标识位UPDATE,则代表其是需要更新的节点
    if (currentFiber.type === ELEMENT_TEXT) {
      if (currentFiber.alternate.props.text !== currentFiber.props.text) {
        currentFiber.stateNode.textContent = currentFiber.props.text
      }
    }
  }
  currentFiber.effectTag = null
}

/**
* 根据一个 fiber 的 effect list 更新视图
*/
const commitRoot = () => {
  let currentFiber = workInProgressRoot.firstEffect
  while (currentFiber) {
    commitWork(currentFiber)
    currentFiber = currentFiber.nextEffect
  }
  currentRoot = workInProgressRoot // 把当前渲染成功的根fiber赋给currentRoot
  workInProgressRoot = null
}

3.2.2 Complete view update

The next step is to execute the work in a loop. When the effect list of each fiber is calculated, commitRoot is called to complete the view update.

 const workloop = (deadline) => {
  let shouldYield = false // 是否需要让出控制权
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
    shouldYield = deadline.timeRemaining() < 1 // 如果执行完任务后,剩余时间小于1ms,则需要让出控制权给浏览器
  }
  if (!nextUnitOfWork && workInProgressRoot) {
    console.log('render阶段结束')
    commitRoot() // 没有下一个任务了,根据effect list结果批量更新视图
  }
  // 请求浏览器进行再次调度
  requestIdleCallback(workloop, { timeout: 1000 })
}

4. Summary

Compared with the traditional Stack architecture, Fiber divides the work into multiple work units, and each work unit decides whether to give up control to the browser to perform rendering according to the remaining time after the execution is completed. And it sets the priority of each unit of work, suspends, reuses and aborts the unit of work. Each Fiber node is a node on the fiber tree, connected by children, siblings, and back references to form a complete fiber tree.


xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2和《Android应用开发实战》