Connected to aThe React principle of Fiber
In this article, we will introduce you to the first rendering logic of React Fiber
New project
To create a React application, use create-react-app
- Delete unnecessary files from SRC and keep them
index.js
file - check
package
The react version is17
If not, change the React version to16
- React17 adopted
runtime
Mode, not introduced at the beginning of the fileReact
By default, react will be used for rendering, so we need to reduce version to 16 and use our own react implementation for rendering - First render with native React and React -dom
import React from 'react';
import ReactDOM from 'react-dom';
let style = { border: '3px solid red'.margin: '5px' };
let element = (
<div id="A1" style={style}>
A1
<div id="B1" style={style}>
B1
<div id="C1" style={style}>
C1
</div>
<div id="C2" style={style}>
C2
</div>
</div>
<div id="B2" style={style}>
B2
</div>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
Copy the code
Virtual Dom
Introduction to virtual Dom
- When the project is packaged, the JSX syntax is transformed via Babel, which replaces the JSX syntax with the react.createElement () method to create the virtual DOM
- You can see the transformed code on the Babel website
- The react.createElement () argument, the first of which is the type of the node, the second of which is the property of the node, and the children of the node, will be nested in the DOM tree
implementationcreateElement
methods
- new
react
Folder, where we put our own react
- Constants.js Put in some constants that need to be defined
// The type attribute indicates that this is a text element fiber {tag:TAG_TEXT,type:ELEMENT_TEXT}
export const ELEMENT_TEXT = Symbol.for('ELEMENT_TEXT');
/ / the tag attributes
//React applications require a Fiber root
export const TAG_ROOT = Symbol.for('TAG_ROOT');
// Native node span div p function component class component
export const TAG_HOST = Symbol.for('TAG_HOST');
// This is a text node
export const TAG_TEXT = Symbol.for('TAG_TEXT');
// This is class component
export const TAG_CLASS = Symbol.for('TAG_CLASS');
// Function components
export const TAG_FUNCTION_COMPONENT = Symbol.for('TAG_FUNCTION_COMPONENT');
/ / effectTag properties
// Insert the node
export const PLACEMENT = Symbol.for('PLACEMENT');
// Update the node
export const UPDATE = Symbol.for('UPDATE');
// Delete a node
export const DELETION = Symbol.for('DELETION');
Copy the code
- React. js corresponds to the original React library, which is exported
createElement
Method, which is responsible for creating the virtual DOM
// react.js
import { ELEMENT_TEXT } from './constants';
/** * How to create a virtual Dom *@param {*} Type Element type div SPAN P *@param {*} Config Configures object element attributes *@param {... any} Children all the sons, so this is going to be an array */
function createElement(type, config, ... children) {
delete config.__self;
delete config.__source;
// If it is a text type, return the element object
const newChildren = children.map(child= > {
// Child is a React element returned by React. CreateElement
if (typeof child === 'object') return child;
// child is a string
return {
type: ELEMENT_TEXT,
props: { text: child, children: []}}; });return {
type,
props: {
...(config || {}), // May be null
children: newChildren,
},
};
}
const React = {
createElement,
};
export default React;
Copy the code
- React. js corresponds to the native React-DOM library and is exported
render
Method is responsible for page rendering
import { TAG_ROOT } from './constants';
import { scheduleRoot } from './scheduler';
/** * Render an element inside the container *@param {*} Element renders the element virtual Dom *@param {*} Container Indicates the node to be mounted. */
function render(element, container) {
let rootFiber = {
tag: TAG_ROOT, / / root fiber
stateNode: container, // Real DOM node
// Props. Children is an array containing the React element, and Fiber is created for each React element
props: { children: [element] },
};
// Start scheduling
scheduleRoot(rootFiber);
}
const ReactDOM = {
render,
};
export default ReactDOM;
Copy the code
Implement scheduling core logic
- Scheduler.js core scheduling logic, export
scheduleRoot
Methods forrender
Method call, passing in the root fiber to perform scheduling logic
import { TAG_HOST, TAG_ROOT, TAG_TEXT, PLACEMENT } from './constants';
import { reconcileChildren } from './reconcileChildren';
import { setProps } from './utils';
let workInProgressRoot = null; // The root Fiber is being rendered
let nextUnitOfWork = null; // Next unit of work
// To be exposed to the outside
export function scheduleRoot(rootFiber) {
workInProgressRoot = rootFiber;
nextUnitOfWork = workInProgressRoot;
}
// 1. The work loop is executed at the end of each frame
function workLoop(deadline) {
let shouldYield = false; // Whether to pause
while(nextUnitOfWork && ! shouldYield) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork); shouldYield = deadline.timeRemaining() <1;
}
if(! nextUnitOfWork && workInProgressRoot) {console.log('End of reconciliation phase');
commitRoot();
}
WorkLoop is executed once for each frame to check if there are any tasks to execute
requestIdleCallback(workLoop, { timeout: 500 });
}
// 2. Execute work on each fiber
function performUnitOfWork(currentFiber) {
beginWork(currentFiber);
// Go through the eldest son first
if (currentFiber.child) {
return currentFiber.child;
}
while (currentFiber) {
// Complete first without child nodes
completeUnitOfWork(currentFiber);
// See if there are any brothers
if (currentFiber.sibling) {
return currentFiber.sibling;
}
// The child nodes are done, let the parent complete, the parent is root node, return is null, break the while loopcurrentFiber = currentFiber.return; }}// 3. Get to work
// Two functions: 1. Create real DOM 2. Create sub-fiber
function beginWork(currentFiber) {
switch (currentFiber.tag) {
case TAG_ROOT:
updateHostRoot(currentFiber);
break;
case TAG_TEXT:
updateHostText(currentFiber);
break;
case TAG_HOST:
updateHost(currentFiber);
break;
default:
break; }}// The stateNode is passed in externally
function updateHostRoot(currentFiber) {
const newChildren = currentFiber.props.children;
reconcileChildren(currentFiber, newChildren);
}
// Text type
function updateHostText(currentFiber) {
// No real DOM is created
// It is a text type with no child nodes and no need to execute reconcileChildren
if (!currentFiber.stateNode) {
currentFiber.stateNode = createDOM(currentFiber);
}
}
// Native type
function updateHost(currentFiber) {
// No real DOM is created
if(! currentFiber.stateNode) { currentFiber.stateNode = createDOM(currentFiber); }const newChildren = currentFiber.props.children;
reconcileChildren(currentFiber, newChildren);
}
// Create the real DOM
function createDOM(currentFiber) {
if (currentFiber.tag === TAG_TEXT) {
return document.createTextNode(currentFiber.props.text);
} else if (currentFiber.tag === TAG_HOST) {
// span div these native tags
const stateNode = document.createElement(currentFiber.type);
updateDOM(stateNode, {}, currentFiber.props);
returnstateNode; }}// Update the properties of the real DOM
function updateDOM(stateNode, oldProps, newProps) {
// It exists and is a real DOM
if(stateNode && stateNode.setAttribute) { setProps(stateNode, oldProps, newProps); }}// 4. Finish the work, collect the fiber with side effects, and build the Effect List
function completeUnitOfWork(currentFiber) {
let returnFiber = currentFiber.return;
if(! returnFiber)return;
if(! returnFiber.firstEffect) { returnFiber.firstEffect = currentFiber.firstEffect; }if (currentFiber.lastEffect) {
if (returnFiber.lastEffect) {
// make the last unit's lastEffect. NextEffect point to the next unit's firstEffect
returnFiber.lastEffect.nextEffect = currentFiber.firstEffect;
}
returnFiber.lastEffect = currentFiber.lastEffect;
}
// If there are effectTags, you need to collect them
if (currentFiber.effectTag) {
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = currentFiber;
} else{ returnFiber.firstEffect = currentFiber; } returnFiber.lastEffect = currentFiber; }}// ----------- Commit phase -----------
function commitRoot() {
let currentFiber = workInProgressRoot.firstEffect;
console.log(workInProgressRoot.firstEffect);
while (currentFiber) {
commitWork(currentFiber);
currentFiber = currentFiber.nextEffect;
}
// Commit completed
workInProgressRoot = null;
}
function commitWork(currentFiber) {
if(! currentFiber)return;
let returnFiber = currentFiber.return;
const domReturn = returnFiber.stateNode;
if (currentFiber.effectTag === PLACEMENT && currentFiber.stateNode) {
// If it is a new DOM node
domReturn.appendChild(currentFiber.stateNode);
}
currentFiber.effectTag = null;
}
requestIdleCallback(workLoop, { timeout: 500 });
Copy the code
performUnitOfWork
methods
- The traversal order and completion order of the main control nodes
- Traversal order: self -> son -> brother
- Finish order: son -> myself -> brother -> Father
function performUnitOfWork(currentFiber) {
// 1. Go through yourself first
beginWork(currentFiber);
// 2
if (currentFiber.child) {
return currentFiber.child;
}
while (currentFiber) {
// 3.1 Complete first without child nodes
// 3.2 Son and brother finish, let yourself finish
completeUnitOfWork(currentFiber);
// 4. Check if there are any younger brothers
if (currentFiber.sibling) {
return currentFiber.sibling;
}
If the parent is root, return is null, and the while loop is brokencurrentFiber = currentFiber.return; }}Copy the code
beginWork
methods
- 1. Create a real Dom based on different types and check
stateNode
Does it have a value? If not, create it - 2. Perform
reconcileChildren
Method, according to the child node virtual DOM, create fiber, build fiber tree
function beginWork(currentFiber) {
switch (currentFiber.tag) {
case TAG_ROOT:
/ / the root node
updateHostRoot(currentFiber);
break;
case TAG_TEXT:
// Text node
updateHostText(currentFiber);
break;
case TAG_HOST:
// Native tags
updateHost(currentFiber);
break;
default:
break; }}// The stateNode is passed in externally
function updateHostRoot(currentFiber) {
const newChildren = currentFiber.props.children;
reconcileChildren(currentFiber, newChildren);
}
// Text type
function updateHostText(currentFiber) {
// No real DOM is created
// It is a text type with no child nodes and no need to execute reconcileChildren
if (!currentFiber.stateNode) {
currentFiber.stateNode = createDOM(currentFiber);
}
}
// Native type
function updateHost(currentFiber) {
// No real DOM is created
if(! currentFiber.stateNode) { currentFiber.stateNode = createDOM(currentFiber); }const newChildren = currentFiber.props.children;
reconcileChildren(currentFiber, newChildren);
}
// Create the real DOM
function createDOM(currentFiber) {
if (currentFiber.tag === TAG_TEXT) {
return document.createTextNode(currentFiber.props.text);
} else if (currentFiber.tag === TAG_HOST) {
// span div these native tags
const stateNode = document.createElement(currentFiber.type);
updateDOM(stateNode, {}, currentFiber.props);
returnstateNode; }}// Update the properties of the real DOM
function updateDOM(stateNode, oldProps, newProps) {
// It exists and is a real DOM
if(stateNode && stateNode.setAttribute) { setProps(stateNode, oldProps, newProps); }}Copy the code
- Update/set DOM properties
- Click events are set up natively, without react compositing events
/ / utils. Js,
// Set attributes for the element
export function setProps(dom, oldProps, newProps) {
for (let key in oldProps) {
if(key ! = ='children') {
if (newProps.hasOwnProperty(key)) {
setProp(dom, key, newProps[key]); // If there are both old and new, then update
} else {
dom.removeAttribute(key); // The old props has this attribute, but the new props does not}}}for (let key in newProps) {
if(key ! = ='children') {
if(! oldProps.hasOwnProperty(key)) {// The old one does not exist, but the new one doessetProp(dom, key, newProps[key]); }}}}// Set a single
function setProp(dom, key, value) {
if (/^on/.test(key)) {
//onClick
dom[key.toLowerCase()] = value; // There is no composite event
} else if (key === 'style') {
if (value) {
for (let styleName invalue) { dom.style[styleName] = value[styleName]; }}}else{ dom.setAttribute(key, value); }}Copy the code
reconcileChildren
methods
- According to the virtual DOM of the child node, create the child node fiber and build the fiber tree
- Hang the eldest son in the current Fiber
child
on - The one who hangs his younger brothers on his older son
sibling
on - The first creation identifies side effects
effectTag
Set to the PLACEMENT
import {
ELEMENT_TEXT,
TAG_TEXT,
TAG_HOST,
PLACEMENT,
} from './constants';
/** * create fiber tree *@param {*} CurrentFiber currentFiber *@param {*} NewChildren Children of the current node, the virtual DOM array */
export function reconcileChildren(currentFiber, newChildren) {
let newChildIndex = 0; // The index in the new virtual DOM array
let prevSibling;
while (newChildIndex < newChildren.length) {
const newChild = newChildren[newChildIndex];
let tag;
if (newChild && newChild.type === ELEMENT_TEXT) {
tag = TAG_TEXT; / / text
} else if (newChild && typeof newChild.type === 'string') {
tag = TAG_HOST; // Native DOM component
}
/ / create the fiber
let newFiber = {
tag, // Native DOM component
type: newChild.type, // The specific element type
props: newChild.props, // New property object
stateNode: null.//stateNode must be empty
return: currentFiber, Fiber / / parent
effectTag: PLACEMENT, // Side effect identifier
nextEffect: null};// Build the fiber list
if (newChildIndex === 0) {
currentFiber.child = newFiber; // The first child is attached to the child property of the parent
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber; // Then newFiber becomes the last brothernewChildIndex++; }}Copy the code
Collect effects to build an Effect List
completeUnitOfWork
When you’re done, collect the fibers that have side effects and build a side effect linked list- The linked list passes through the fiber property
firstEffect
nextEffect
lastEffect
To build
Build the Effect List step
Step1. The eldest son with no children completes first
Both firstEffect and lastEffect of returnFiber and currentFiber are empty returnFiber. FirstEffect = eldest returnFiber. LastEffect = eldest
Step2. Complete the younger brother without child node
FirstEffect = First returnFiber. LastEffect = firstEffect and lastEffect of currentFiber are empty
ReturnFiber. LastEffect. NextEffect = currentFiber = brother; ReturnFiber. LastEffect = currentFiber = brother;
Step3. Complete the parent node
CurrentFiber. FirstEffect = elder currentFiber. LastEffect = Younger Brother Elder currentFiber. NextEffect = younger brother
returnFiber.firstEffect = currentFiber.firstEffect; (Parent’s parent. FirstEffect = older son)
returnFiber.lastEffect = currentFiber.lastEffect; (Parent of the parent. LastEffect = younger brother)
ReturnFiber. LastEffect (brother). NextEffect = currentFiber(parent);
ReturnFiber. LastEffect = currentFiber;
Step4. Complete the next unit
Make a unit lastEffect. NextEffect point to the next unit firstEffect returnFiber. LastEffect. NextEffect = currentFiber. FirstEffect;
function completeUnitOfWork(currentFiber) {
let returnFiber = currentFiber.return;
if(! returnFiber)return;
if(! returnFiber.firstEffect) { returnFiber.firstEffect = currentFiber.firstEffect; }if (currentFiber.lastEffect) {
if (returnFiber.lastEffect) {
// make the last unit's lastEffect. NextEffect point to the next unit's firstEffect
returnFiber.lastEffect.nextEffect = currentFiber.firstEffect;
}
returnFiber.lastEffect = currentFiber.lastEffect;
}
// If there are effectTags, you need to collect them
if (currentFiber.effectTag) {
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = currentFiber;
} else{ returnFiber.firstEffect = currentFiber; } returnFiber.lastEffect = currentFiber; }}Copy the code
Submit CommitRoot
- After the node traversal is completed and the effect list is collected, the submission stage is entered
- The commit phase iterates through the collected Effect List and applies the changes to the real Dom
- This process is not interrupted
function commitRoot() {
let currentFiber = workInProgressRoot.firstEffect;
console.log(workInProgressRoot.firstEffect);
while (currentFiber) {
commitWork(currentFiber);
currentFiber = currentFiber.nextEffect;
}
// Commit completed
workInProgressRoot = null;
}
function commitWork(currentFiber) {
if(! currentFiber)return;
let returnFiber = currentFiber.return;
const domReturn = returnFiber.stateNode;
if (currentFiber.effectTag === PLACEMENT && currentFiber.stateNode) {
// If it is a new DOM node
domReturn.appendChild(currentFiber.stateNode);
}
currentFiber.effectTag = null;
}
Copy the code
Implement first render
- Replace the react and react-dom libraries in the entry file with your own and start the project
// Write react yourself
import React from './react/react';
import ReactDOM from './react/react-dom';
Copy the code
- Implementation effect
The code in
commit
First render logic
If there are loopholes and deficiencies, please correct them