React16 version than before to update the process of virtual DOM is recursively using cycle, there is a problem with this way of comparison, the task is once begun cannot be interrupted, if an array in large Numbers, the main thread is occupied by a long, until the entire virtual DOM tree alignment update after completion of the main line Cheng Cai is released, The main thread can only perform other tasks, which can cause some user interaction or animation tasks to not be performed immediately, and the page will lag, which can greatly affect the user experience.
The main reason is that recursion cannot be terminated, and it takes a long time to execute heavy tasks. Javascript is single-threaded, and other tasks cannot be executed at the same time, resulting in task delay, page lag, and poor user experience.
In plain English, Fiber is a new DOM comparison algorithm. The previous algorithm was called STACK. It was because of the above problems in stack algorithm that the React official rewrote the comparison algorithm. Fiber uses the browser’s free time to execute tasks and refuses to tie up the main thread for long periods of time. Discard recursion and just use loops, because loops can be broken. Break big tasks into smaller ones. The comparison of the entire DOM tree was a task, but now the comparison of each node is a task.
In Fiber scheme, DOM alignment algorithm is divided into two parts in order to realize termination and termination of tasks. The first part is virtual DOM alignment, and the second part is real DOM update. The virtual DOM alignment can be terminated, but the update of the real DOM cannot.
Babel converts the JSX syntax into a call to the React. CreateElement method, which returns a virtual DOM object, and then performs the first stage, which is to build the Fiber object. We loop through the virtual DOM to find each internal object, building Fiber objects for each of the virtual DOM objects. Fiber is also a JS object similar to the virtual DOM object.
{
type: "Node type".props: "Node Properties".stateNode: "Node DOM object or component instance object".tag: "Node mark".effects: ['Store fiber objects that need to be changed'].effectTag: "Current operation to be performed on Fiber".parent: "Parent of current Fiber".child: "Subclass of current Fiber Fiber".sibling: "Current Fiber's brother Fiber.".alternate: "Fiber backup is used for comparison."
}
Copy the code
This stores more information, of which the most important is the effectTag. When all the Fiber nodes have been generated, they need to be stored in the array, and then the second stage can be performed to loop the Fiber array. The loop determines what the current node is doing based on the effectTag and applies it to the real DOM.
In summary, for initial rendering, we need to first build Fiber objects and then store them in an array. Then, we need to apply the operations that Fiber needs to do to the real DOM. For state update, we need to rebuild all Fiber objects and then obtain the old Fiber objects for comparison, and then form Fiber array. Then apply Fiber to the real DOM.
Here actually has a problem, when the second phase of the all Fiber object in the same array is their relationship was completely erased, and every DOM elements in a web page is a child element, we need to know who’s who of the child the parent who is who, who is who, at the same level so that we can accurately construct a DOM tree, So the Fiber object also stores the parent, child, and brother levels of the current node.
requestIdleCallback
RequestIdleCallback is the core API used in React-Fiber. It is used to execute tasks using the free time of the browser. If there is a higher priority task to execute, the current task can be terminated and the higher priority task can be executed first.
RequestIdleCallback This is a browser-built method that is directly mounted on Windows and can be used directly. Receives a callback function that takes as an argument an object in which the timeRemaining method returns milliseconds of free time.
requestIdleCallback(function(deadline) {
// todo
// deadline.timeremaining () gets the free time of the browser
})
Copy the code
We all know that pages are drawn frame by frame, and when the number of frames drawn per second reaches 60 the page is smooth, but less than that the user will feel sluggish. If the execution time of each frame is less than 16ms, it indicates that the browser has free time. If the task is not completed in the remaining time, the task execution will stop and the main task will continue to be executed first. That is to say, requestIdleCallback always uses the free time of the browser to execute the task.
We can test this by having two buttons on the page, a div, and clicking on the first button to do something expensive and long on the main thread, and clicking on the second button to change the background color of the div on the page.
<div class="playground" id="play">playground</div>
<button id="work">start</button>
<button id="interaction">handle some user</button>
<style>
.playground {
background: red;
padding: 20px;
margin-bottom: 10px;
}
</style>
Copy the code
var play = document.getElementById('play');
var workBtn = document.getElementById('work');
var interationBtn = document.getElementById('interaction');
var number = 100000000;
var value = 0;
function calc() {
while (number > 0) {
value = Math.random() > 0.5 ? value + Math.random() : value + Math.random()
number--;
}
}
workBtn.addEventListener('click'.function() {
calc();
})
interationBtn.addEventListener('click'.function() {
play.style.background = 'green'
})
Copy the code
We know that changing the background color will not work if the thread is tied up for a long time, the page will take a while to change the background color, which is very unfriendly, we can use requestIdleCallback to solve this problem.
When the first button is clicked we can call the requestIdleCallback API and put the calc function in it.
workBtn.addEventListener('click'.function() {
requestIdleCallback(calc);
})
Copy the code
At this time, Calc will receive a parameter named Deadline, and use the timeRemaining method of this parameter to obtain free time. When the free time is greater than 1ms, we will perform the task.
function calc(deadline) {
while (number > 0 && deadline.timeRemaining() > 1) {
value = Math.random() > 0.5 ? value + Math.random() : value + Math.random() number--; }}Copy the code
At this time, when the user clicks the button with a higher priority, it will continue to execute the code that switches the background first, so that the cyclic task stops. Here we also add requestIdleCallback(calc) at the bottom of the function to indicate that the execution will continue at the next idle time, otherwise it will stop.
function calc(deadline) {
while (number > 0 && deadline.timeRemaining() > 1) {
value = Math.random() > 0.5 ? value + Math.random() : value + Math.random()
number--;
}
requestIdleCallback(calc);
}
Copy the code
Creating a Task Queue
We are going to implement the Fiber algorithm. First we need a piece of JSX code. We are going to see how we can use Fiber to convert JSX into DOM objects to display on the page.
First we need to set up a development environment, I have set up here, you can help yourself. link
The project relies on the following modules and their specific functions are analyzed.
dependency | describe |
---|---|
webpack | Module packaging tool |
webpack-cli | Package command tool |
webpack-node-externals | Remove modules from the node_modules folder when packing server-side modules |
@babel/core | ES conversion tool |
@babel/preset-env | Babel presets convert advanced javaScript syntax |
@babel/preset-react | Convert JSX syntax |
babel-loader | Webpack tool Babel loader |
nodemon | Monitor server file changes and restart applications |
npm-run-all | Command line tool that can execute multiple commands at the same time |
express | Web development framework based on Node platform |
Add the following code to SRC /index.js. We know that JSX code is converted by Babel into a call to the react. createElement method, so we need to introduce React, In order to better analyze it, we wrote the ReactReact source code analysis in the previous chapter. Here we will directly copy the createElement method into the project and introduce it.
const jsx = <div>
<p>Hello Fiber</p>
</div>
console.log(jsx);
Copy the code
And you can see that the console output an object, and you can see that the first tag is div, the child element of the P tag, the child element of the P tag is text text element, code address
{
"type": "div"."props": {
"children": [{"type": "p"."props": {
"children": [{"type": "text"."props": {
"children": []."textContent": "Hello Fiber"}}]}}Copy the code
Next we need to introduce render to render the virtual DOM into the page and introduce the Render method.
import React, { render } from './react';
const jsx = <div>
<p>Hello Fiber</p>
</div>
const root = document.getElementById('root');
render(jsx, root)
Copy the code
We need to define the Render method, which takes two parameters, the virtual DOM and the container to render.
There are two things you need to do in this function. The first is to add a task to the task queue, and the second is to specify that the task should be executed when the browser is idle. A task is something to do, such as building a Fiber object through the virtual DOM. A task queue is an array because there is more than one task. So tasks are placed in a task queue, which is an array.
const render = (element, dom) = >{}Copy the code
We also need to define a queue, createTaskQueue, to which we can add and read content.
const createTaskQueue = () = > {
const taskQueue = [];
return {
push: item= > { // Add tasks to the task queue
return taskQueue.push(item);
},
pop: () = > { // Get the task from the task queue
return taskQueue.shift();
},
isEmpty: () = > { // Determine whether a task exists
taskQueue.length === 0}}}export default createTaskQueue;
Copy the code
A task is then added to the queue in the Render method. A task is an object with two attributes, parent and child, linked to the address
import { createTaskQueue } from '.. /Misc';
const taskQueue = createTaskQueue();
export const render = (element, dom) = > {
// 1. Add a task to the task queue
taskQueue.push({
dom, / / parent
props: {
children: element / / the child}})// 2. Specify to execute the task when the browser is idle
}
Copy the code
Implement task scheduling logic
Next we implement the scheduling logic for the task. In the Render method we call the requestIdleCallback API to execute the task when the browser is idle.
const render = (element, dom) = > {
// 1. Add a task to the task queue
taskQueue.push({
dom, / / parent
props: {
children: element / / the child}})// 2. Specify to execute the task when the browser is idle
requestIdleCallback(performTask)
}
Copy the code
When requestIdleCallback is called, it needs to pass in a function that will be called when the browser is idle. This function is called performTask. So let’s define this method.
This method only schedules the task and does not execute it. We create a workLoop method to execute the task. Pass in the deadline argument.
const performTask = deadline= > {
workLoop(deadline)
}
Copy the code
We also need to define the workLoop method, which is the real work. First we need to determine if the task exists. First we define a subTask to store the current task, default value is null, so in the workLoop we first determine whether the subTask exists. If not, we call getFirstTask to get a task.
let subTask = null;
const getFirstTask = () = >{}const workLoop = deadline= > {
if(! subTask) { subTask = getFirstTask() } }Copy the code
We execute the task if it exists. Because there are multiple tasks, we are going to use a loop here. Check that the subTask exists and the browser has more than 1ms free time before executing the task. The code that performs the task is handled separately in the executeTask function. The object that executeTask receives is essentially a Fiber.
ExecuteTask needs to return a new task after execution.
const executeTask = fiber= >{}const workLoop = deadline= > {
if(! subTask) { subTask = getFirstTask() }while (subTask && deadline,timeRemaining() > 1) { subTask = executeTask(subTask); }}Copy the code
We know that the requestIdleCallback will be interrupted if there is a higher priority task executing it and the method will exit, i.e. PerformTask, so we need to check if the subTask has a value. If it has a value, the task is not finished. We also need to determine if there are values in the task queue. The source address
const performTask = deadline= > {
workLoop(deadline);
if(subTask || ! taskQueue.isEmpty()) { requestIdleCallback(performTask) } }Copy the code
Task Execution – Build Fiber object
Before the task is executed, we first need to clarify what the task is to be executed and how the task should be executed.
Our task is to build Fiber objects for each node based on virtual DOM exclusivity. The implementation is also simple, such as the following object. First build the outermost div object, and then build the two divs inside, when the three nodes are completed to set the corresponding relationship between them.
Note that for the parent div, only the first div is the child, and the rest are the siblings of the child div. When the relationship between the three of them is established, go to the first child node of the parent, child, to see if this node has children, there is a p. When p is constructed, the link is constructed. There are no children and no siblings. At this point, continue to look at the parent to see if the parent has siblings, and if so, determine if it has been built and then check the child nodes of that node. If not, build and then check the child nodes.
<div class="parent">
<div class="child">
<p></p>
</div>
<div class="sibling">
<p></p>
</div>
</div>
Copy the code
Let’s implement the above process. In this JSX, root is the parent node and div is the child node, which we passed in with the Render method.
import React, { render } from './react';
const jsx = <div>
<p>Hello Fiber</p>
</div>
const root = document.getElementById('root');
render(jsx, root)
Copy the code
In the render method we have written the task scheduling logic, now we write the logic to build Fiber, in the getFirstTask method we first need to get a subTask from the task queue. Then build the Fiber object corresponding to the outermost node.
So the first thing I’m going to get here is the first little task, not the first task in the task queue, the first little task in the task queue. We treat the task in the task queue as a large task from which to fetch the first subtask. Construct the outermost Fiber node object. Here we return the Fiber object.
{
type: "Node type".props: "Node Properties".stateNode: "Node DOM object or component instance object".tag: "Node mark".// host_root host_component class_component function_component
effects: ['Store fiber objects that need to be changed'].effectTag: "Current operation to be performed on Fiber".// Add delete modify
parent: "Parent of current Fiber".child: "Subclass of current Fiber Fiber".sibling: "Current Fiber's brother Fiber.".alternate: "Fiber backup is used for comparison."
}
Copy the code
The type attribute is not required for the outermost node, so we omit it. Sibling and alternate are not required, and child is null for now.
const getFirstTask = () = > {
// Get the task from the task queue
const task = taskQueue.pop();
// Return the outermost node Fiber object
return {
props: task.props,
stateNode: task.dom,
tag: 'host_root'.effects: [].child: null,}}Copy the code
The value returned by getFirstTask is assigned to the subTask, and when the subTask has a value and the browser has free time, the loop is called and executeTask is executed. Now that the parent node is built, it’s time to start building the child node. The parameter executeTask receives is subTask.
To construct a child node, you need to get the virtual DOM object of the child node, subTask. Props. Children, subTask is fiber, so you can get the virtual DOM object of the child node from fiber.
const executeTask = fiber= > {
reconcileChildren(fiber, fiber.props.children)
}
Copy the code
Here we do this by calling the reconcileChildren method, passing in the fiber and the virtual DOM objects of the child nodes. Since the parent-child relationship is to be established, both parameters are passed.
The children here could be a DOM object or it could be an array, and we’ll deal with that before we build the children. We treat it as an array, and if it’s an object, we wrap an array around the object.
And then we’re going to take the virtual DOM in arrifiedChildren and turn it into Fibler, and we’re using loops here. Three variables are defined here, index and number for the loop, and Element stores the DOM object currently traversed.
const arrified = arg= > Array.isArray(arg) ? arg : [arg]
const reconcileChildren = (fiber, children) = > {
// Convert children to an array
const arrifiedChildren = arrified(children);
let index = 0;
let numberOfElements = arrifiedChildren.length;
let element = null;
while(index < numberOfElements) { element = arrifiedChildren[index]; index++; }}Copy the code
Element is the child node we need. Now we can build a Fiber object. We declare a newFiber object. Its value is null by default, and the corresponding Fiber object is retrieved when looped.
const reconcileChildren = (fiber, children) = > {
// Convert children to an array
const arrifiedChildren = arrified(children);
let index = 0;
let numberOfElements = arrifiedChildren.length;
let element = null;
// Fiber is currently being built
let newFiber = null;
// Store the previous node to build the sibling relationship
let prevFiber = null;
while (index < numberOfElements) {
element = arrifiedChildren[index];
newFiber = {
type: element.type,
props: element.props,
tag: 'host_component'.effects: [].effectTag: 'placement'./ / new
stateNode: null.// Dom object, not yet
parent: fiber,
}
// If the first child is assigned to fiber
if (index == 0) {
fiber.child = newFiber;
} else {
// Otherwise put it on the sibling of the previous nodeprevFiber.sibling = newFiber; } prevFiber = newFiber; index++; }}Copy the code
The fiber object has a property called stateNode. The value of this property depends on the type of the current node. If the current node is a normal node, it stores the DOM object of the current node, and if it is a component, it stores the instance object of the component. So we’re going to declare a way to do that. This method takes the current Fiber object as a parameter.
CreateDOMElement (createDOMElement, createDOMElement, createDOMElement, createDOMElement)
// Get the node object
newFiber.stateNode = createStateNode(newFiber);
const createStateNode = fiber= > {
// Common node
if (fiber.tag === 'host_component') {
return createDOMElement(fiber)
}
}
Copy the code
There is also a tag attribute in the Fiber object, which indicates whether the current node is a tag node or a component node. This also requires a function to determine.
const getTag = vdom= > {
if (typeof vdom.type === 'string') {
return 'host_component'
}
}
newFiber = {
type: element.type,
props: element.props,
tag: getTag(element),
effects: [].effectTag: 'placement'./ / new
stateNode: null.// Dom object, not yet
parent: fiber,
}
Copy the code
Note that you do not need to call getTag to get the root node. The root node is always the host_root string.
At this point we are done building the outer node object and the first subset node object, and then we start to find the node and continue to build the node object. We previously built the outer node objects and the first child node objects through the reconcileChildren method, and when the first subset is built, the code returns to this function. Here we check if Fiber has a child and return if so. This will return a new task when executeTask completes execution.
After executing executeTask, it will execute executeTask again. The second execution passes fiber as fiber.child and executes the child as the parent. Continue building children of children.
const executeTask = fiber= > {
// Build the sub-fiber object
reconcileChildren(fiber, fiber.props.children)
if (fiber.child) {
return fiber.child
}
}
const workLoop = deadline= > {
// Get a task if the subtask does not exist
if(! subTask) { subTask = getFirstTask() }// The task exists and the browser has more than 1ms free time to execute the task
while (subTask && deadline.timeRemaining() > 1) { subTask = executeTask(subTask); }}Copy the code
This code is a little simpler, but a little harder to understand, so you can think about it for yourself. The source address
Next, we continue to build Fiber objects of other nodes. In the code above, we have built a node of a link. At this time, we must locate the last node of a link, and we need to find other nodes according to this node to build Fiber objects of other nodes.
The search principle is very simple, if the current node has a sibling, to build the child of the same level, if the current node does not have a sibling, to check whether the parent has a sibling, so that the search can always build all the nodes. The build is complete when it finally returns to the root node.
const executeTask = fiber= > {
// Build the sub-fiber object
reconcileChildren(fiber, fiber.props.children)
// Have child returns child
if (fiber.child) {
return fiber.child
}
// Store the object currently being processed
let currentExecutelyFiber = fiber;
while (currentExecutelyFiber.parent) {
// there are sibling returns to sibling
if (currentExecutelyFiber.sibling) {
return currentExecutelyFiber.sibling
}
// No sibling passes parent to loop, loop checks parent
currentExecutelyFiber = currentExecutelyFiber.parent
}
}
Copy the code
We have now found all the nodes and are building Fiber objects for all of them. The source address
Build the effects
In the second phase of the Fiber algorithm, we loop through all fibers to build real DOM objects and add the DOM objects to the page, so we store all fiber objects in an array for easy operation.
The Effects array is where the Fiber objects are stored. We want to store all the Fiber objects in the Effects array of the outermost node object.
All nodes have Effects objects, the outermost node is responsible for storing all fiber objects, and the other nodes are responsible for collecting fiber objects. And then it goes to the outermost layer. We can add this logic to the loop code.
In the while loop, the currentExecutelyFiber is the fiber object being operated on, and you can find the parent effects array. We’re going to make this array equal to its own value plus the value of the effects of the current currentExecutelyFiber plus the current currentExecutelyFiber.
while (currentExecutelyFiber.parent) {
// The current parent's Effects store the current effects and the current object.
currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat(currentExecutelyFiber.effects.concat(currentExecutelyFiber))
// there are sibling returns to sibling
if (currentExecutelyFiber.sibling) {
return currentExecutelyFiber.sibling
}
// No sibling passes parent to loop, loop checks parent
currentExecutelyFiber = currentExecutelyFiber.parent
}
Copy the code
So now we’re storing all the child objects in the parent’s effects.
Implement initial render
When the loop completes, the currentExecutelyFiber stores the outermost node object, and its effects allow us to retrieve the fiber object for all nodes. Then we can move on to the second phase of the Fiber algorithm to build the DOM node relationships and add them to the page.
So the first thing we’re going to do here is change currentExecutelyFiber to a global variable, because we’re going to pass it along. Define pendingCommit to receive this variable.
const executeTask = fiber= >{... pendingCommit = currentExecutelyFiber; }Copy the code
When fiber is completed, the workLoop is executed, where we check if pendingCommit exists, and if it does, we call the method to pass in this parameter.
const workLoop = deadline= > {
// Get a task if the subtask does not exist
if(! subTask) { subTask = getFirstTask() }// The task exists and the browser has more than 1ms free time to execute the task
while (subTask && deadline.timeRemaining() > 1) {
subTask = executeTask(subTask);
}
// Execute phase 2
if (pendingCommit) {
commitAllWork(pendingCommit)
}
}
Copy the code
In the commitAllWork method we loop through the Effects array and append it to the parent if its effectTag type is placement, that is, when a new node is added.
const commitAllWork = fiber= > {
fiber.effects.forEach(item= > {
// Append the node
if (item.effectTag === 'placement') { item.parent.stateNode.appendChild(item.stateNode); }})}Copy the code
Render class component
We want to render the content returned from the class component into the page, and we need to return this type in our getTag method.
const getTag = vdom= > {
// If it is a common node
if (typeof vdom.type === 'string') {
return 'host_component'
} else if (Object.getPrototypeOf(vdom.type) === Component) {
// If it is a class component
return 'class_component'
} else {
// Function components
return 'function_component'}}Copy the code
Then we have to deal with the stateNode property, storing the DOM object if it’s a normal node or the instance object if it’s a component, and handling this logic in the createStateNode method.
If the component is a class component and a function component, we also need a method to determine. CreateReactInstance ().
const createStateNode = fiber= > {
// Common node
if (fiber.tag === 'host_component') {
return createDOMElement(fiber)
} else {
/ / component
return createReactInstance()
}
}
Copy the code
The difference between a function component and a class component is that the fiber object has a tag attribute, which is a class component if its value is class_component otherwise it is a function component. Return the constructor if it is a class component, or equal to type if it is a function component.
createReactInstance = fiber= > {
let instance = null;
if (fiber.tag === 'class_component') {
instance = new fiber.type(fiber.props)
} else {
instance = fiber.type;
}
return instance;
}
Copy the code
Now that we have the stateNode and Tag values in place, the parent argument should be Fiber and the child argument should be the return value in the Render method when we call the reconcileChildren method. So we cannot directly pass fiber.props. Children when implementing this method, so make a judgment before calling the executeTask reconcileChildren, and deal with it according to the Fiber tag type.
const executeTask = fiber= > {
// Build the sub-fiber object
if (fiber.tag === 'class_component') {
reconcileChildren(fiber, fiber.stateNode.render())
} else {
reconcileChildren(fiber, fiber.props.children)
}
...
}
Copy the code
Then need to continue processing in commitAllWork method, class component itself is also a node, but can’t be additional DOM object class components, because the component class itself is not a valid DOM elements, we need to find the class components of the parent node, his parent must be common DOM elements, if not continue to search until I find it, We append the content returned by the class component to the parent of the normal DOM element. The source address
const commitAllWork = fiber= > {
fiber.effects.forEach(item= > {
// Append the node
if (item.effectTag === 'placement') {
let fiber = item;
let parentFiber = item.parent
while (parentFiber.tag === 'class_component') {
parentFiber = parentFiber.parent;
}
if (fiber.tag === 'host_component') { parentFiber.stateNode.appendChild(fiber.stateNode); }}})}Copy the code
Render function component
In Fiber, the stateNode stores the function itself, and the tag stores the function_component string, which we wrote earlier. And then we’re going to find the place to call which is executeTask, and when we’re dealing with a function component its child node needs to be called by the function component, and we’re going to add an else if we’re dealing with a function component.
if (fiber.tag === 'class_component') {
reconcileChildren(fiber, fiber.stateNode.render())
} else if (fiber.tag === 'function_component') {
reconcileChildren(fiber, fiber.stateNode(fiber.props))
} else {
reconcileChildren(fiber, fiber.props.children)
}
Copy the code
Then we enter the commitAllWork. In the loop, we not only need to determine whether the parent is a class component, but also a function component. If it is a function component, it cannot append real DOM elements itself, and we also need to look for the ancestor of ordinary DOM nodes.
while (parentFiber.tag === 'class_component' || parentFiber.tag === 'function_component') {
parentFiber = parentFiber.parent;
}
Copy the code
We can also test the props parameter. It works. The source address
Implement node update
When the DOM node is initialized, we need to back up the old Fiber node object. When updating the DOM node, we need to check whether the old Fiber node exists. If so, we need to create the Fiber node object that performs the update.
We first back up the node object. In the Reconciliation commitAllWork method, we can back up the Fiber node object after DOM execution is complete. Let’s just back it up to the actual DOM root object.
fiber.stateNode.__rootFiberContainer = fiber;
Copy the code
Next we create the getFirstTask method of the Fiber node object, we need to add an alternate property to store the backup Fiber object. This will have a backup in the new root object.
const getFirstTask = () = > {
// Get the task from the task queue
const task = taskQueue.pop();
// Return the outermost node Fiber object
return {
props: task.props,
stateNode: task.dom,
tag: 'host_root'.effects: [].child: null.alternate: task.dom.__rootFiberContainer
}
}
Copy the code
We then determine what operations to execute in the reconcileChildren method to build fiber objects for each operation. Let’s get the backup node first.
Get the child node if there is a fiber.alternate object, from fiber.alternate. Child, representing the child node. In this case, fiber.alternate.child is the secondary subnode, and the element obtained from children during the first loop in the while is the current child node.
let alternate = null;
// Get child nodes if there are objects
if (fiber.alternate && fiber.alternate.child) {
// If found, the child node is the backup node of the first node in the children array
alternate = fiber.alternate.child;
}
while (index < numberOfElements) {
// Child virtual DOM objects
element = arrifiedChildren[index];
}
Copy the code
At the same time, we need to know that only the first child node is child, and the rest are Sibling, so we need to take Sibling when updating alternate.
/ / update the alternate
if (alternate && alternate.sibling) {
alternate = alternate.sibling;
} else {
alternate = null;
}
Copy the code
If element exists, alternate does not exist is the initial render, if both exist is the update operation. Update creates the Fiber object that performs the update, changes the effectTag to update, and stores the alternate.
if (element && alternate) {
// Update operation
newFiber = {
type: element.type,
props: element.props,
tag: getTag(element),
effects: [].effectTag: 'update'./ / new
stateNode: null.// Dom object, not yet
parent: fiber,
alternate,
}
if (element.type === alternate.type)
newFiber.stateNode = createStateNode(newFiber);
}
Copy the code
Also determine whether the type to be updated is the same as that of the backup. If the type is different, you don’t need to compare, just replace the old one with the new one. If the type is the same, you need to compare.
if (element.type === alternate.type) {
// Same type
newFiber.stateNode = alternate.stateNode;
} else {
// Different types
newFiber.stateNode = createStateNode(newFiber);
}
Copy the code
We then add update logic to the commitAllWork method. If item.effectTag is update, it is update. The update operation determines whether the nodes are the same or different, so we also have two cases.
if (item.effectTag === 'update') {
// Update operation
if (item.type === item.alternate.type) {
// The node type is the same
} else {
// Different node types}}else if (item.effectTag === 'placement') {
Copy the code
If the node type is different, replace the old node with the new node. We need to find the parent node to operate on. Call replaceChild and update the node if the node type is the same. The first parameter to updateNodeElement is the DOM node to update, the second parameter is the new virtual DOM, and the third parameter is the old virtual DOM.
// Update operation
if (item.type === item.alternate.type) {
// The node type is the same
updateNodeElement(item.stateNode, item, item.alternate);
} else {
// Different node types
item.parent.stateNode.replaceChild(item.stateNode, item.alternate.stateNode);
}
Copy the code
One problem with this code is that we can only update element nodes, not text nodes, and we need to update the updateNodeElement method to update text nodes.
We add a piece of logic at the top that handles the text node. If it’s a text node, we don’t let it go down because the code below executes the element node.
This is where we check to see if the new text node is the same as the old one, and update it if it is different. We also need to determine whether the current node has the same parent. If so, do this operation. If not, additional processing is required. The source address
if (virtualDOM.type === 'text') {
if(newProps.textContent ! == oldProps.textContent) {// Determine whether the parent nodes are the same, if not, append the current text to the parent
if(virtualDOM.parent.type ! == oldVirtualDOM.parent.type) { virtualDOM.parent.stateNode.appencChild(document.createTextNode(newProps.textContent))
} else {
// The new text node replaces the old text node
virtualDOM.parent.stateNode.replaceChild(document.createTextNode(newProps.textContent), oldVirtualDOM.stateNode); }}return;
}
Copy the code
Implementing node deletion
We need to determine in the reconcileChildren method whether the reconcileChildren is currently being deleted. If the element does not exist and the backup node does exist, then the deletion is done.
// If element does not exist, alternate exists, delete
if(! element && alternate) {// Delete a node
alternate.effectTag = 'delete'
// Add to the parent's Effects array
fiber.effects.push(alternate);
}
Copy the code
We need to update the loop condition, if the array is empty the loop cannot enter, but alternate is possible, so we need to change.
while (index < numberOfElements || alternate) {
Copy the code
NewFiber does not exist if Element does not exist, so we need to check below.
// If the first child is assigned to fiber
if (index == 0) {
fiber.child = newFiber;
} else if (element) {
// Otherwise put it on the sibling of the previous node
prevFiber.sibling = newFiber;
}
Copy the code
Finally we need to do the actual DOM operation on the commitAllWork to delete the node, if item.effectTag === ‘delete’ is currently performing the delete operation.
if (item.effectTag === 'delete') {
item.parent.stateNode.removeChild(item.stateNode);
} else if (item.effectTag === 'update') {
Copy the code
Class component status updates
To queue the task when the Component state changes and specify that it should be executed when the browser is idle, we add a setState method to the Component class that calls the scheduleUpdate method to update it.
class Component {
constructor(props) {
this.props = props;
}
setState(partialState) {
scheduleUpdate(this, partialState)
}
}
Copy the code
ScheduleUpdate is defined in the RECONCILIATION JS for mutual calls with previous methods. This method takes the component instance object and the new state parameters.
Get the state from the instance object and override it with the new state. Don’t forget to add the updated task to the task queue. From defines a new task type for easy differentiation.
Finally, don’t forget to add requestIdleCallback(performTask) to execute the task queue when the browser is idle.
const scheduleUpdate = (instance, partialState) = > {
taskQueue.push({
from: 'class_component',
instance,
partialState
})
requestIdleCallback(performTask)
}
Copy the code
This type of task is handled in the getFirstTask get task method. I’m going to go back and not let him go down, so I’m going to do the new task.
const getFirstTask = () = > {
// Get the task from the task queue
const task = taskQueue.pop();
if (task.from === 'class_component') {
return;
}
// Return the outermost node Fiber object
return {
props: task.props,
stateNode: task.dom,
tag: 'host_root'.effects: [].child: null.alternate: task.dom.__rootFiberContainer
}
}
Copy the code
Check whether you’re dealing with a class component in the commitAllWork loop by setting the tag as class_component. If it is a class component, add the backup property storage fiber to the instance object of that component.
if (item.tag === 'class_component') {
// Handle class components
item.stateNode.__fiber = item;
}
Copy the code
We now back up the component’s Fiber object on the component instance object. So we can get the fiber object from getFirstTask. Task. The instance. __fiber.
if (task.from === 'class_component') {
const root = getRoot(task.instance);
return {
props: root.props,
stateNode: root.stateNode,
tag: 'host_root'.effects: [].child: null.alternate: root // root is a backup
};
}
Copy the code
We look up until we find the outermost node object.
const getRoot = instance= > {
let fiber = instance.__fiber
while (fiber.parent) {
fiber = fiber.parent
}
return fiber;
}
Copy the code
Now we are going to update the state. Before updating the state, we need to store the state to be updated
const root = getRoot(task.instance);
// Store the state to be updated
task.instance.__fiber.partialState = task.partialState;
Copy the code
In the executeTask method, if it is a class component, we see if the partialState exists and update the component if it does.
// Build the sub-fiber object
if (fiber.tag === 'class_component') {
/ / update the state
if(fiber.stateNode.__fiber && fiber.stateNode.__fiber.partialState) { fiber.stateNode.state = { ... fiber.stateNode.state, ... fiber.stateNode.__fiber.partialState } } reconcileChildren(fiber, fiber.stateNode.render()) }else if (fiber.tag === 'function_component') {
Copy the code
This concludes the basic principles of React-Fiber.
The source address