React15’s rendering and diff will recursively compare the vDom tree to find the nodes that have been changed (added, deleted, or changed) and then update them synchronously. If the number of nodes on the page is too large, React will constantly tie up browser resources, resulting in unresponsive user actions. Here’s how Time management master Fiber helps React balance business (browser tasks) and love (trigger responses).

What is the Fiber

Fiber is not a new term in computer terminology. Its Chinese translation is called “fiber”, which is the same execution process as Progress, Thread and Coroutine. React Fiber is an update mechanism within React. Supports different priorities of tasks, interrupts and recovers, and reuses the saved state after recovery. Each work update unit is the Fiber node corresponding to the React Element.

React Fiber’s core interaction with the browser

The page is rendered in frames. In general, the refresh rate of the device is 1s 60 times, that is, the number of frames drawn per second (FPS) is less than 60, the page will appear obvious lag, so the drawing time of a frame should not exceed 16.7ms (this time is very important).

Scheduler is added to The Scheduler list in Update 16 compared to Update 15.

First, React requests scheduling from the browser. If the browser has free time within a frame (16.7ms), the browser will determine whether there is any task to be executed. If there is any task to be executed, the browser will determine whether there is still time until the browser completes the task. Fiber is a data structure (stack frame) and a solution for interruptible invocation tasks. Its features are time slicing and Supense.

Updates go from being recursive to a cycle that can be interrupted. Each time the loop calls shouldYield to see if there is any time left.

/ / the react source packages/react - the reconciler/SRC/ReactFiberWorkLoop. New. Js
function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  // Listen to the scheduler, he said to execute, execute
  while(workInProgress ! = =null&&! shouldYield()) { workInProgress = performUnitOfWork(workInProgress); }}Copy the code

Scheduler

We need a mechanism to tell us if the browser has time. RequestIdleCallback is the key API for implementing this mechanism. It allows users to respond quickly to their operations without blocking their interactions. Surprisingly, it does not reduce computation, but only takes the time to fragmentation to the extreme. React abandoned it and implemented the more fully featured requestIdleCallback Polyfill itself, which is called Scheduler.

Scheduler is a library independent of React

The structure of the Fiber

/ / the react source packages/react - the reconciler/SRC/ReactInternalTypes
{
    
    type: any, // For class components, it points to the constructor; For DOM elements, it specifies the HTML tag
    key: null | string, // Unique identifier
    stateNode: any, // Save references to component class instances, DOM nodes, or other React element types associated with fiber nodes
    child: Fiber | null./ / the eldest son
    sibling: Fiber | null.// Next brother
    return: Fiber | null./ / the parent node
    tag: WorkTag, / / define the type of fiber operation, see https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactWorkTags.js
    nextEffect: Fiber | null.// Pointer to the next node
    updateQueue: mixed, // Queue for status updates, callback functions, AND DOM updates
    memoizedState: any, // Create the fiber state for the output
    pendingProps: any, // Has been updated from new data in the React element and needs to be applied to props for child components or DOM elements
    memoizedProps: any, // Props used to create output during the previous render
    / /...
}

Copy the code
  • type & key

    Fiber’s type and key act the same way as React elements. Fiber’s type describes its corresponding component; for composite components, type is the function or class component itself. For native tags (div, SPAN, etc.), type is a string. With different types, key is used during reconciliation to determine whether Fiber can be used again.

  • stateNode

    StateNode holds references to component class instances, DOM nodes, or other React element types associated with fiber nodes. In general, you can think of this property as holding local state related to fiber.

  • child & sibling & return

    The child property points to the first child of this node. The sibling attribute points to the next sibling of this node (the older son points to the second son, and the second son points to the third son). The return property refers to the parent node of this node, to whom the current node should submit its work once it has finished processing. If a fiber has multiple subfibers, the return fiber of each subfiber is parent.

I’m curious why the parent pointer is called return instead of parent. The child Fiber node and its sibling nodes return to their parent node when they complete their work.

Fiber traversal process

If the emperor died, the throne would pass to his eldest son. If the emperor had no heirs, the throne would pass to his brother. If there was no direct blood line, the throne would pass to his uncle until the dragon veins dried up and the dynasty ended.

The execution sequence is A1 B1 C1 C2 B2 C3 C4

We write this depth-first traversal by hand according to this rule

// see the source code can also learn algorithm 🐶
// iterate over the function
const performUnitOfWork = (Fiber) = > {
  // The crown prince is the first in line
  if (Fiber.child) {
    return Fiber.child
  }
  while (Fiber) {
    // The second line of succession
    if (Fiber.sibling) {
      return Fiber.sibling
    }
    // This branch is cut off, go back to choose uncle
    Fiber = Fiber.return
  }
}

const workloop = (nextUnitOfWork) = > {
  // If the execution unit is to be executed, the next execution unit is returned
  while (nextUnitOfWork) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
  }
  if(! nextUnitOfWork) {console.log('End of Reconciliation Stage')
  }
}

workloop(rootFiber)

// rootFiber is constructed in the back while writing
Copy the code

Native debug source code

Let’s take a look at printing a Fiber node in an actual project

We’ll still use create-react-app to create a project. Debugging the source code requires some configuration in the project

  • NPM Run eject(exposes webPack configuration)
  • Add the React source folder to the SRC file
  • Change the react reference path in webpack

I won’t go into details about how to debug the React source code locally, or clone it using my configured project.

This allows us to debug the source code locally

// packages/react-dom/src/ReactDOMLegacy.js
function legacyRenderSubtreeIntoContainer(parentComponent: ? React$Component<any, any>, children: ReactNodeList, container: Container, forceHydrate: boolean, callback: ?Function.) {
  if (__DEV__) {
    topLevelUpdateWarnings(container);
    warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
  }

  // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  // member of intersection type." Whyyyyyy.
  let root: RootType = (container._reactRootContainer: any);
  let fiberRoot;
  if(! root) {// Initial mount Initial render
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }

    // Initial mount should not be batched.
    // First render is non-batch update, which can ensure update efficiency and user experience
    unbatchedUpdates(() = > {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // Update
    updateContainer(children, fiberRoot, parentComponent, callback);
  }

  console.log('fiber-----current', fiberRoot.current); // Here we print the fiber tree
  return getPublicRootInstance(fiberRoot);
}
Copy the code
// We write JSX like this, div has an H1 tag and an A tag
const jsx = (
   <div className="content">I am a<a href="www.baidu.com">Ming is not</a>

   </div>
 )
Copy the code

Take a look at the print

Let’s draw a linked list diagram of fiber

Let’s write a fiber by hand

We can use the React source code to parse (a) the handwritten render function code

Here we only focus on fiber’s type, props, stateNode, child, Sibling, return attributes. // Fiber js object.

// We construct a fiber node in the render function
let wipRoot = null;
function render(vnode, container) {
  wipRoot = {
    type: "div".props: {
      children: {... vnode}, },stateNode: container,
  };
  nextUnitOfWOrk = wipRoot;
}

// The native label node receives the fiber node where work is executing
function updateHostComponent(workInProgress) {
  const {type, props} = workInProgress;
  // Consider whether a node already exists before inserting it
  if(! workInProgress.stateNode) { workInProgress.stateNode = createNode(workInProgress); } reconcileChildren(workInProgress, workInProgress.props.children);console.log("workInProgress", workInProgress); 
}

// Coordinate child nodes
function reconcileChildren(workInProgress, children) {
  if (typeof children === "string" || typeof children === "number") {
    return;
  }
  // wrap an element after React16, or an array
  const newChildren = Array.isArray(children) ? children : [children];
  // The last fiber node
  let previousNewFiber = null;
  for (let i = 0; i < newChildren.length; i++) {
    let child = newChildren[i];
    // Construct a new fiber node
    let newFiber = {
      type: child.type,
      props: {... child.props},stateNode: null.child: null.sibling: null.return: workInProgress,
    };

    if (i === 0) {
      // The first child fiber, the first son
      workInProgress.child = newFiber;
    } else {
      // No to brother
      previousNewFiber.sibling = newFiber;
    }

    // Record a fiberpreviousNewFiber = newFiber; }}function performUnitOfWork(workInProgress) {
  // step1 execute the task
  // todo
  const {type} = workInProgress;
  if (typeof type === "string") {
    // Native label node
    updateHostComponent(workInProgress);
  }

  // step2 and return to the next execution task
  if (workInProgress.child) {
    return workInProgress.child;
  }

  let nextFiber = workInProgress;
  while (nextFiber) {
    if (nextFiber.sibling) {
      returnnextFiber.sibling; } nextFiber = nextFiber.return; }}function workLoop(IdleDeadline) {
  while (nextUnitOfWOrk && IdleDeadline.timeRemaining() > 1) {
    // Execute the task and return to the next execution task
    nextUnitOfWOrk = performUnitOfWork(nextUnitOfWOrk);
  }

  / / submit
  if (!nextUnitOfWOrk && wipRoot) {
    commitRoot();
  }
}
// Implement time slice
requestIdleCallback(workLoop);

// End of scheduling, render
function commitRoot() {
  commitWorker(wipRoot.child);
  wipRoot = null;
}

// Submit the work execution result
function commitWorker(workInProgress) {
  // Commit yourself
  if(! workInProgress) {return;
  }

  let parentNodeFiber = workInProgress.return;
  let parentNode = parentNodeFiber.stateNode;

  if (workInProgress.stateNode) {
    parentNode.appendChild(workInProgress.stateNode);
  }

  // Submit the child node
  commitWorker(workInProgress.child);

  // Submit sibling nodes
  commitWorker(workInProgress.sibling);
}



Copy the code

The effect of

The code has been uploaded to the Git code address

When mounting the components, the Reconciler generates the corresponding Fiber nodes for the components according to the contents of the components described by THE JSX. A DOM tree is constructed using depth first traversal.

conclusion

The Fiber architecture is the cornerstone of React, which uses time slicing to transform synchronous updates into interruptible asynchronous updates. This time, we learned about the interaction flow between Fiber and the browser, and implemented a simple Fiber tree. The traversal flow is traversal first based on depth. But what is not covered here is the prioritization mechanism, how to continue breakpoints, and how to collect task results. Future articles will examine these mechanisms and see how React is updated by diff.

Refer to the link

React Technology Reveals

React Technology revealed

Fully understand React Fiber

Introduction to the React Fiber

React Fiber

Step into the React Fiber world