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 themindex.jsfile
  • checkpackageThe react version is17If not, change the React version to16
  • React17 adoptedruntimeMode, not introduced at the beginning of the fileReactBy 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

implementationcreateElementmethods

  • newreactFolder, 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 exportedcreateElementMethod, 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 exportedrenderMethod 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, exportscheduleRootMethods forrenderMethod 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

performUnitOfWorkmethods

  • 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

beginWorkmethods

  • 1. Create a real Dom based on different types and checkstateNodeDoes it have a value? If not, create it
  • 2. PerformreconcileChildrenMethod, 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

reconcileChildrenmethods

  • 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 Fiberchildon
  • The one who hangs his younger brothers on his older sonsiblingon
  • The first creation identifies side effectseffectTagSet 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

  • completeUnitOfWorkWhen you’re done, collect the fibers that have side effects and build a side effect linked list
  • The linked list passes through the fiber propertyfirstEffect nextEffect lastEffectTo 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