What is the Fiber
Fiber can be considered the smallest unit of work in the Reat, fragmenting the update process. Fiber and Scheduler cooperate to realize the interruption and recovery of tasks: when a task with a higher priority is encountered during the execution of a task, the current task will be interrupted and the high-priority task will be executed first. After the high-priority task is completed, the interrupted task will be resumed.
For Scheduler, see the previous article Scheduler on React source parsing
During the update process, Fiber will create a Fiber tree based on the React element depth traversal. Each React element will have a Fiber object:
React Element tree: Fiber tree: APP APP ↓ ↓ div div ↓ ↓ ↓ h1 H2 H1 H2Copy the code
The structure of the Fiber object is as follows:
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag; // Current fiber types: ClassComponent, FunctionComponent, HostComponent...
this.key = key;
this.elementType = null; // Class components refer to component classes. HostComponent refers to tag types: div, span...
this.type = null; // Like elementType, a class component points to the component's class. HostComponent points to the tag type: div, span...
this.stateNode = null; // Fiber instance, class component points to component instance, HostComponent points to DOM element
/ / Fiber linked list
this.return = null; // Point to the parent fiber
this.child = null; // Point to the sublevel fiber
this.sibling = null; // Point to brother Fiber
this.index = 0;
this.ref = null; / / ref
// Update the correlation
this.pendingProps = pendingProps; // The property to be updated
this.memoizedProps = null; // Props for the last update
this.updateQueue = null; // Update the queue
this.memoizedState = null; // Last updated state
this.dependencies = null;
this.mode = mode;
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
// This is related to the priority of lanes
this.lanes = NoLanes; // Indicates whether the current node needs to be updated
this.childLanes = NoLanes; // Indicates whether children of the current node need to be updated
// Attributes associated with nodes in the current and workInProgress trees
// Can be used to determine whether to update or create, with a value indicating update and a value indicating creation
this.alternate = null;
}
Copy the code
The concept of a double buffer tree
There are two trees in the React update process, one called the Current tree and the other called the workInprogress tree.
The current tree corresponds to the rendered content on the screen, and the workInprogress tree traverses the new Fiber tree first according to the depth of the current tree. All the content that needs to be updated will be reflected in the workInprogress tree. When the update is not completed, only the corresponding contents of the Current tree will always be displayed on the screen. When the update is completed, the Current tree will be switched to the workInprogress tree, and the workInprogress tree will become the new Current tree.
Build the Fiber tree
Here’s how to build the Fiber tree.
Let’s start with the entry function:
ReactDOM.createRoot(root).render(<Demo1 />);
Copy the code
First we call the createRoot function and go directly to the source code:
export function createRoot(container: Container, options? : CreateRootOptions,) :RootType {...// Create the container-fiber root node
constroot = createContainer( container, ConcurrentRoot, hydrate, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, ); .return new ReactDOMRoot(root);
}
Copy the code
Looking only at the code related to creating Fiber, you can see that the createContainer function is called:
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
) :OpaqueRoot {
return createFiberRoot(
containerInfo,
tag,
hydrate,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
);
}
Copy the code
CreateFiberRoot is called inside the createContainer function:
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
) :FiberRoot {
In the React project, there is only one Fiber root node
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// Create root fiber nodes
const uninitializedFiber = createHostRootFiber(
tag,
isStrictMode,
concurrentUpdatesByDefaultOverride,
);
// Associate fiber root with root fiberroot.current = uninitializedFiber; uninitializedFiber.stateNode = root; .// Initialize the update queue on root fiber
initializeUpdateQueue(uninitializedFiber);
return root;
}
Copy the code
You can see that a FiberRootNode instance is created and then the createHostRootFiber function is called. This creates a FiberNode instance and returns, FiberRoot instance is then associated with FiberNode instance using current attribute, FiberNode instance is associated with FiberRoot instance using stateNode attribute. FiberRootNode instance is called FiberRoot. The FiberNode instance is called Root Fiber, since subsequent render calls are mounted to root Fiber, so we can think of root Fiber as the root of the Render content.
In the React project, only one fiber root node exists.
Their relationship is as follows:
Finally, return fiber root, then take the returned Fiber root in createRoot and create an instance of ReactDOMRoot with it as a parameter.
Let’s look at the source code for ReactDOMRoot:
function ReactDOMRoot(internalRoot) {
this._internalRoot = internalRoot;
}
ReactDOMRoot.prototype.render = function(children: ReactNodeList) :void {
const root = this._internalRoot; // fiber root. updateContainer(children, root,null.null);
};
Copy the code
You can see that ReactDOMRoot assigns fiber root to the private _internalRoot property.
Render (
You can see that the Render method calls updateContainer and passes in children, which is the React Element object we passed in when we called the Render method.
Here you have the basic Fiber tree.
Use Scheduler to schedule tasks. See the priority Lane model for React source code parsing and the Scheduler for React source code parsing.
Create WorkInprogress
When the task starts executing, the prepareFreshStack function is called, which creates the workInprogress tree:
function prepareFreshStack(root: FiberRoot, lanes: Lanes) {... workInProgress = createWorkInProgress(root.current,null); . }Copy the code
You can createWorkInProgress by calling createWorkInProgress from this function, passing root.current as an argument. Root is fiber root, and root.current is root fiber.
Let’s go to createWorkInProgress and see how to createWorkInProgress:
export function createWorkInProgress(current: Fiber, pendingProps: any) :Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) { workInProgress = createFiber( current.tag, pendingProps, current.key, current.mode, ); workInProgress.elementType = current.elementType; workInProgress.type = current.type; workInProgress.stateNode = current.stateNode; . workInProgress.alternate = current; current.alternate = workInProgress; }else {
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
workInProgress.flags = NoFlags;
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null; . }... workInProgress.flags = current.flags & StaticMask; workInProgress.childLanes = current.childLanes; workInProgress.lanes = current.lanes; workInProgress.child = current.child; workInProgress.memoizedProps = current.memoizedProps; workInProgress.memoizedState = current.memoizedState; workInProgress.updateQueue = current.updateQueue;const currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext, }; workInProgress.sibling = current.sibling; workInProgress.index = current.index; workInProgress.ref = current.ref; .return workInProgress;
}
Copy the code
As you can see, first get the alternate property of the fiber node on the current tree, i.e. the corresponding fiber node on workInProgress. If the Fiber node of workInProgress is not empty, it will be reused and the pendingProps will be updated. Being empty recreates a new Fiber object associated with the Fiber node in the Current tree using the alternate property.
Alternate function is in the next update, to judge whether alternate has a value, there is an update, no is to create.
The Fiber tree looks like this:
The renewal of the Fiber
The React update process is divided into two phases:
- Render phase
- The commit phase
The Render phase is interruptible, while the Commit phase is non-interruptible.
During the Render phase, each Fiber node goes through two more phases:
- BeginWork phase
- CompleteWork phase
The main work of the beginWork stage is to construct the workInprogress tree by traversing down from root fiber to determine the final state of the fiber to be updated by diff algorithm.
The completeWork stage is the main work, which starts from the last node of the deep traversal and goes back up, and checks if there are any sibling nodes, the beginWork process will be executed again, and goes back up if there are no sibling nodes. During the backtracking process, collect the attributes that need to update fiber and mount them to the updateQueue updateQueue under the fiber node, and add the flags for the update. Each traceback mounts the flags of the child node to subtreeFlags of the parent node.
Next, the source code parsing of beginWork and completeWork will be written into separate chapters.