[react framework] Don’t make Fiber so difficult to understand, let’s refer to how I understand it, and explain it in vernacular

Article directory

  • Preface
  • Background of the emergence of Fiber: problems caused by responsive update features
  • Benefits brought by Fiber: splitting tasks and executing them on demand
  • Fiber simple principle description
  • What does a Fiber object look like?
  • What key api is used to realize the principle
  • The difference between Fiber and Vue’s responsive update
  • Linked list structure of hooks

Foreword

Watching videos on Bilibili and some blogs on the Internet, when talking about fiber, the source code is always explained, and all kinds of high-level conceptual vocabulary are extremely unfriendly to people who are coming into contact with fiber for the first time and want to understand it.

So I read this and that on the Internet, combined my own understanding, sorted it out, and wrote this article explaining fiber in vernacular, hoping to help newcomers.

There are still many loose parts in the article, but these are all for ease of understanding. I believe that when learning something, you must first understand the general outline and then add details.

Background of the emergence of Fiber: problems caused by responsive update features

Friends who have used react should know that when we modify a certain node data of a component, the component will re-update each node, including all sub-components, which is what everyone often calls, top-down Next re-render the component.

If you don’t believe it, you can use node rendering {Math.random()} to verify.

During each re-rendering process, a new virtual DOM tree will be regenerated.

If the virtual tree is very complex (for example, there are many complex sub-components nested), the main thread will occupy the time for a long time when doing diff, and the time left for rearrangement and redrawing cannot be guaranteed to be executed 60 times in 1 second, and the page will be It will get stuck (if you don’t understand here, you can read [Intersection of Computer Principles] to discuss and sort out how browsers parse HTML files).

This is a pain point of react15. In order to solve this pain point, version 16 launched fiber.

Benefits brought by Fiber: splitting tasks and executing on demand

To put it simply, using fiber can cut the diff task into very small tasks. Every time you want to do these small tasks, check whether the main thread has an idle period every 16.6ms, and if so, insert them into the execution.

And he also has the ability. To give a loose analogy, for example, a certain diff task needs to be divided into 100 small tasks. He does not divide them all at once, but inserts the main tasks every 16.6ms while dividing. Executed while the thread is idle.

Simple principle description of Fiber

The essence of fiber is actually a js object, which is a packaging object in the intermediate process from the virtual dom node object to the view.

When traversing the virtual tree in the diff algorithm, depth first is used. In react15, the traversal must be completed all at once, that is, the traversal task is only once.

Then we can transform each virtual dom node, and record the node objects pointing to parent, child, and sibling nodes.

What are the benefits of this? When the traversal task is interrupted, we can record the location of the traversed node. When we continue to traverse next time, we can find the node where the traversal task is suspended through the recorded position, and continue traversing through the pointing relationship.

OK, then can we use this idea to segment fine-grained tasks?

The process of adding real DOM attributes to each virtual DOM node and adding various relationship pointing is a simple small task, also called Unit Task. I call it fiberization.

For example, if a DOM tree has 100 nodes, it can be divided into 100 unit tasks and needs to be fiberized 100 times.

This process can be described as follows:

When the main thread is idle, it traverses the virtual tree and fiberizes each node. At this time, if 20 nodes are fiberized, 20 unit tasks are completed. At this time, the main thread is occupied by other tasks and records the traversed node positions. Then the main thread becomes idle again and continues to traverse from the 20th node until all the child nodes found after returning to the root node have been fiberized, and all tasks are completed.

What does a Fiber object look like

I’ll give you an excerpt from the Internet. In fact, you just need to know the direction and the type:

type Fiber = {<!-- -->
  // The WorkTag type used to mark the fiber, mainly represents the component type represented by the current fiber, such as FunctionComponent, ClassComponent, etc.
  tag: WorkTag,
  // key in ReactElement
  key: null | string,
  // ReactElement.type, the first parameter of calling `createElement`
  elementType: any,
  // The resolved function/class/ associated with this fiber.
  // Indicates the node type currently represented
  type: any,
  //Represents the element component instance corresponding to the current FiberNode
  stateNode: any,

  // Point to his `parent` in the Fiber node tree, used to return upward after processing this node
  return: Fiber | null,
  // point to its first child node
  child: Fiber | null,
  // Points to its own sibling structure, and the return of the sibling node points to the same parent node
  sibling: Fiber | null,
  index: number,

  ref: null | (((handle: mixed) => void) & amp; {<!-- --> _stringRef: ?string }) | RefObject,

  // The component props object in the current processing process
  pendingProps: any,
  // props after the last rendering
  memorizedProps: any,

  // Updates generated by the component corresponding to the Fiber will be stored in this queue.
  updateQueue: UpdateQueue<any> | null,

  //The state of the last rendering
  memorizedState: any,

  // A list to store the context that this Fiber depends on
  firstContextDependency: ContextDependency<mixed> | null,

  mode: TypeOfMode,

  //Effect
  // Used to record Side Effect
  effectTag: SideEffectTag,

  //Singly linked list is used to quickly find the next side effect
  nextEffect: Fiber | null,

  //The first side effect in the subtree
  firstEffect: Fiber | null,
  //The last side effect in the subtree
  lastEffect: Fiber | null,

  // Represents the time point at which the task should be completed in the future. Later versions will be renamed lanes
  expirationTime: ExpirationTime,

  // Quickly determine if there are changes in the subtree that are not waiting for
  childExpirationTime: ExpirationTime,

  //fiber version pool, which records the fiber update process for easy recovery
  alternate: Fiber | null,
}

What key APIs should be used to implement the principle?

In fact, strictly speaking, we are asking about the implementation principle of Scheduler, because it is he who does the task segmentation and dispatch.

I heard on the Internet that it was implemented using the pseudo requestIdleCallback that was used before. Because the compatibility of requestIdleCallback is very poor, for example, Safari does not support it directly.

Then react18 used MessageChannel

If you want to know more about it, you can refer to Why React Scheduler is implemented using MessageChannel

The difference between responsive updates between Fiber and Vue

Anyone who has used Vue knows that Vue’s responsive update is a precise update. For example, if a node of a component changes, just update this node. The proxy is used (vue2 is defineProperty), but each responsive variable requires a proxy, and each component collects a lot of dependencies, so the performance is not perfect.

I can only say that both have their own merits.

Linked list structure of hooks

We can find such an attribute memorizedState on the fiber object. This attribute records the calling sequence of hooks inside the component, for example:

const [ str, setStr ] = useState('a')
useEffect(() => {<!-- -->
  //
    return () => {<!-- -->
        //
    }
})
useLayoutEffect(() => {<!-- -->
    //
    return () => {<!-- -->
        //
    }
})

The relationship in memorizedState is probably like this: useState--->useEffect--->useLayoutEffect

Because it is necessary to record the linked list relationship of this hook, hooks can only be used in the highest-level scope. (For example, if there is a conditional statement that also contains a hook, where in the linked list is it inserted? So it is impossible to let this happen)

If you have the energy, you can read this article for a more in-depth understanding of the linked list of hooks in react: sorting out the principles and differences between useEffect and useLayoutEffect

I haven’t figured it out yet, so I’ll come back to it later when I’m free