1. Problem#
The JavaScript engine and the page rendering engine are two threads that are mutually exclusive. When one thread is executing, the other thread can only be suspended and wait.
If the JavaScript thread occupies the main thread for a long time, the rendering updates will have to wait for a long time. The interface will not be updated for a long time, which will result in poor page responsiveness and users may experience lag.
This is also the problem faced by React 15's Stack Reconciler. When React renders a component, the entire process from start to finish is uninterrupted.
If the component is large, the JavaScript thread will continue to execute, and then wait until the entire VDOM tree is calculated before handing it over to the rendering thread.
This can cause some user interactions, animations, and other tasks to not be processed immediately, resulting in lag.
2. What is it#
React Fiber is a major change and optimization made by Facebook to React over a period of two years. It is a reimplementation of the core algorithm of React. It was confirmed by Facebook at the React Conf 2017 conference that React Fiber was released in React version 16.
In React, the following operations are mainly performed:
- Each task is assigned a priority, and tasks with higher priority can interrupt tasks with lower priority. Then, the lower priority tasks are re-executed.
- Asynchronous tasks are added, using the requestIdleCallback API to execute when the browser is idle.
- The DOM diff tree is transformed into a linked list, where each DOM corresponds to two fibers (a linked list), and two queues correspond to them. This is all for finding interrupted tasks and re-executing them.
From an architectural perspective, Fiber is a rewrite of the React core algorithm (the reconciliation process).
From a coding perspective, Fiber is a data structure defined internally by React. It is a unit node of the Fiber tree structure, which is the virtual DOM in the new architecture of React 16.
A fiber is a JavaScript object that contains information about the element, its update operation queue, and its type. Its data structure is as follows:
type Fiber = {
// Used to mark the WorkTag type of the fiber, mainly indicating the component type represented by the current fiber, such as FunctionComponent, ClassComponent, etc.
tag: WorkTag,
// The key in ReactElement
key: null | string,
// ReactElement.type, the first parameter passed to createElement
elementType: any,
// The type of the current node represented by the fiber
type: any,
// Represents the component instance corresponding to the current FiberNode
stateNode: any,
// Points to its parent in the Fiber node tree, used to return to the parent after processing this node
return: Fiber | null,
// Points to its first child node
child: Fiber | null,
// Points to its sibling node, the sibling node's return points to the same parent node
sibling: Fiber | null,
index: number,
ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,
// Props object of the component during the current processing
pendingProps: any,
// Props after the last rendering is completed
memoizedProps: any,
// The queue where the Updates generated by the Fiber corresponding to this component are stored
updateQueue: UpdateQueue<any> | null,
// The state after the last rendering
memoizedState: any,
// A list that stores the contexts that this Fiber depends on
firstContextDependency: ContextDependency<mixed> | null,
mode: TypeOfMode,
// Effect
// Used to record Side Effects
effectTag: SideEffectTag,
// Single linked list for quickly finding 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, renamed as lanes in later versions
expirationTime: ExpirationTime,
// Quickly determine if there are changes in the subtree that are not waiting
childExpirationTime: ExpirationTime,
// Fiber version pool, records the fiber update process for easy recovery
alternate: Fiber | null,
}
3. How to solve it#
Fiber splits the rendering update process into multiple subtasks, and only does a small part at a time. After completing a part of the task, it checks if there is any remaining time. If there is, it continues to the next task; if not, it suspends the current task and hands over the control to the main thread. When the main thread is not busy, it continues to execute.
It can be interrupted and resumed, and can also reuse the previous intermediate state. Different tasks are assigned different priorities. Each task update unit corresponds to a Fiber node of the React Element.
The implementation of the above approach is through the requestIdleCallback method.
The window.requestIdleCallback() method queues a function to be called during a browser's idle periods. This allows developers to perform background and low-priority work on the main event loop, without impacting latency-critical events such as animations and input response.
First, the tasks in React are divided into multiple steps and completed in batches. After completing a part of the task, the control is returned to the browser, allowing the browser to have time to render the page. When the browser is done with its busy tasks and has spare time, it continues with the unfinished tasks of React. This is a cooperative scheduling.
The implementation process is based on the Fiber node. As a static data structure, each Fiber node corresponds to a React element and saves information about the component type (function component/class component/native component, etc.) and the corresponding DOM node.
As a dynamic unit of work, each Fiber node saves the state changes and work to be performed for the component in the current update.
Each Fiber node has a corresponding React element, and multiple Fiber nodes build a tree based on the following three properties:
// Points to the parent Fiber node
this.return = null
// Points to the child Fiber node
this.child = null
// Points to the first sibling Fiber node on the right
this.sibling = null
These properties can be used to find the next execution target.