preface

Recently began to deeply study the principle of the React, behind will be out of a series of articles about the React principle, basic it is I learn other elder React source code analysis, and follow them to read the source code of some thinking and record, most of the original content, but I will use my own way to summarize principle and related processes, and complement, Take it as a summary of your own learning.

This series focuses on the underlying source code implementation and is not recommended if you are new to React.

React renders the first time.

For the first rendering, the React code becomes the flow of the DOM

There are two main steps. The first is to turn the JSX code into a virtual DOM using the React. CreateElement method, and the second is to turn the virtual DOM into a real DOM using the reactdom.render method.

React.createElement

The React.createElement method may not be known to many newcomers because it is rarely used directly in normal business logic. In fact, the React website already has a description.

JSX will compile to react.createElement (), and react.createElement () will return a JS object called “React Element”.

We are inbabelYou can put one on the websiteJSXHave a try.

After compiling with Babel, JSX becomes a nested react.createElement. JSX is essentially a syntactic sugar for JavaScript calls called React.createElement. With the existence of JSX syntactic sugar, we can use htML-like tag syntax we are most familiar with to create virtual DOM, which not only reduces learning cost, but also improves r&d efficiency and experience.

Next, take a look at the createElement source code

export function createElement(type, config, children) {
  // The propName variable is used to store element attributes that need to be used later
  let propName; 
  // props is the set of key-value pairs used to store element attributes
  const props = {}; 
  // Key, ref, self, and source are all React elements
  let key = null;
  let ref = null; 
  let self = null; 
  let source = null; 

  The config object stores the attributes of the element
  if(config ! =null) { 
    // The first thing you do is assign the ref, key, self, and source attributes in order
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    // String the key here
    if (hasValidKey(config)) {
      key = ' ' + config.key; 
    }
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Move all the properties in config to props one by one
    for (propName in config) {
      if (
        // Filter out properties that can be pushed into the props objecthasOwnProperty.call(config, propName) && ! RESERVED_PROPS.hasOwnProperty(propName) ) { props[propName] = config[propName]; }}}// childrenLength is the number of children of the current element; the 2 subtracted is the length of the type and config parameters
  const childrenLength = arguments.length - 2; 
  // If type and config are omitted, there is only one argument left, which generally means that the text node is present
  if (childrenLength === 1) { 
    // assign this parameter directly to props. Children
    props.children = children; 
    // Handle nested child elements
  } else if (childrenLength > 1) { 
    // Declare an array of children
    const childArray = Array(childrenLength); 
    // Push the child element into the array
    for (let i = 0; i < childrenLength; i++) { 
      childArray[i] = arguments[i + 2];
    }
    // Finally, assign this array to props. Children
    props.children = childArray; 
  } 

  / / defaultProps processing
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) { 
      if (props[propName] === undefined) { props[propName] = defaultProps[propName]; }}}// Finally returns a call to ReactElement execution method, passing in the arguments just processed
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
Copy the code

So let me summarize some of the things that this function does

  • 1. Secondary processingkey.ref.self.sourceFour values (willkeyStringing, willconfigIn therefAssigned toref.selfandsourceI don’t know its function, so I can ignore it.The JSX method in version 17 removes these two parameters directly)
  • 2. Traversalconfig, filter can be assigned topropsIn the properties of the
  • 3. Extract the child element and assign the value toprops.children(If there is only one child element, it is directly assigned, if there is more than one child element, it is stored as an array)
  • 4. The formatdefaultProps(If no relevant is passed inprops.propsTake the default Settings.)
  • 5. Return oneReactElementMethod and pass in the arguments you just processed

CreateElement is essentially a data handler that formats the content retrieved from JSX and passes it into the ReactElement method.

Note: In React 17, createElement will be replaced with the JSX (source address) method.

import React from 'react'; // In version 17, it is possible not to introduce this sentence
function App() {
  return <h1>Hello World</h1>;
}
// createElement
function App() {
  return React.createElement('h1'.null.'Hello world');
}
// JSX in version 17
import {jsx as _jsx} from 'react/jsx-runtime'; // imported by the compiler
function App() {
  // The child element will be compiled directly into the children attribute of the Config object. JSX will no longer accept individual child element inputs
  return _jsx('h1', { children: 'Hello world' });
}
Copy the code

ReactElement

Let’s look at what the ReactElement method does, the source address.

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // REACT_ELEMENT_TYPE is a constant that identifies the object as a ReactElement
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in attribute assignment
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component that created the element
    _owner: owner,
  };

  // 
  if (__DEV__) {
    // Omit unnecessary code
  }

  return element;
};
Copy the code

The ReactElement method is also very simple. It simply creates an object from the parameters passed in and returns it. The ReactElement object instance is what the createElement method finally returns, and is a node in the React virtual DOM. The virtual DOM is essentially an object that stores a number of properties used to describe the DOM.

Note that since each node is called by the createElement method, it should return a tree of the virtual DOM.

const App = (
  <div className="App">
    <h2 className="title">title</h2>
    <p className="text">text</p>
  </div>
);
console.log(App);
Copy the code

As shown in the figure above, all nodes are compiled into ReactElement object instances (virtual DOM).

ReactDOM.render

We have the virtual DOM, but ultimately we want to render the content on the page, so we also need to render the virtual DOM into the real DOM using the reactdom.render method. This is a lot of content, and I will discuss it one function at a time.

As for why to use the virtual DOM, what are the advantages of the virtual DOM, these contents are not the scope of this article, there are many online related articles, you can go to understand.

Three kinds ofReactStart the way

React versions 16 and 17 have always had three boot options

  • legacyMode,ReactDOM.render(<App />, rootNode). In the current common mode, the rendering process is synchronous
  • blockingMode,ReactDOM.createBlockingRoot(rootNode).render(<App />). The transition mode is rarely used
  • concurrentMode,ReactDOM.createRoot(rootNode).render(<App />). The asynchronous rendering mode is also available with some new features that are currently being experimented with.

The official documentation

We are parsing the reactdom.render process, so we are actually analyzing a synchronous process. In terms of Concurrent’s asynchronous rendering process, time sharding and prioritization, these are actually modifications based on synchronous rendering, which I will cover in a later article in this series.

It’s a synchronous process, but in React 16, the entire render link was reconfigured to a Fiber structure. Fiber architecture in React is not strictly equal to asynchronous rendering. It is a design compatible with both synchronous and asynchronous rendering.

For the first timerenderThe three stages of

The reactdom.render method corresponds to a very deep call stack and involves many function methods, but we can just look at some key logic to get a sense of the flow.

The first render can be roughly divided into three stages

  • Initialization phase, completeFiberCreation of a base entity in a tree. From the callReactDOM.renderBegan to,scheduleUpdateOnFiberThe method callperformSyncWorkOnRootThe end.
  • Render phase, build and refineFiberThe tree. fromperformSyncWorkOnRootMethod start, tocommitRootMethod ends.
  • Commit phase, traversalFiberThe trees,FiberNode mapping isDOMNode and render to the page. fromcommitRootMethod starts and ends rendering.

It doesn’t matter if you don’t understand the above methods now, we will take you step by step to understand. You just need to get a sense of what each of the three stages does.

Initialization phase

Now we begin the initialization phase, which, as mentioned above, is all about completing the creation of the basic entities in the Fiber tree. But we need to know what are the basic entities? What are the? We look for the answer in the source code.

legacyRenderSubtreeIntoContainer

We only look at the key logic, now let’s look at ReactDOM. Render legacyRenderSubtreeIntoContainer method (source address) in the call.

  // call in reactdom.render
  return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);

  / / legacyRenderSubtreeIntoContainer source code
  function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
  // Container corresponds to the real DOM object we passed in
  var root = container._reactRootContainer;
  // Initialize the fiberRoot object
  var fiberRoot;
  // The DOM object itself does not have the _reactRootContainer property, so root is empty
  if(! root) {// If root is empty, initialize _reactRootContainer and assign its value to root
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
    / / legacyCreateRootFromDOMContainer create objects can have a _internalRoot attribute, its assigned to fiberRoot
    fiberRoot = root._internalRoot;

    // This is the callback function in the reactdom. render argument
    if (typeof callback === 'function') {
      var originalCallback = callback;
      callback = function () {
        var instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    } // Initial mount should not be batched.
    // Enter the unbatchedUpdates method
    unbatchedUpdates(function () {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    // The else logic deals with the non-first render (i.e. update) case, and the logic is basically the same as upstairs, except that initialization is skipped
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      var _originalCallback = callback;
      callback = function () {
        var instance = getPublicRootInstance(fiberRoot);
        _originalCallback.call(instance);
      };
    } // Update

    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}
Copy the code

This function basically does the following steps

  • 1. CalllegacyCreateRootFromDOMContainerMethod createscontainer._reactRootContainerAnd assigned to itroot
  • 2.rootthe_internalRootAttribute assigned tofiberRoot
  • 3.fiberRootPassed in with some other parametersupdateContainermethods
  • The 4.updateContainerIs passed in as an argumentunbatchedUpdatesmethods

Here,fiberRootThe essence of aFiberRootNodeObject whose associated object is trueDOMThere is one in this objectcurrentobjectAs shown above, this onecurrentThe object is aFiberNodeInstance, in fact, it’s oneFiberNode, and she is still currentFiberThe head node of a tree.fiberRootAnd the one below itcurrentObject and these two nodes will be the whole subsequent treeFiberThe starting point for tree construction.

unbatchedUpdates

Let’s look at the unbatchedUpdates method (source address).

function unbatchedUpdates(fn, a) {
  // This is the context
  var prevExecutionContext = executionContext;
  executionContext &= ~BatchedContext;
  executionContext |= LegacyUnbatchedContext;
  try {
    // The important point here is to call the incoming callback function fn directly, corresponding to the updateContainer method in the current link
    return fn(a);
  } finally {
    // Finally is the processing of the callback queue
    executionContext = prevExecutionContext;
    if (executionContext === NoContext) {
      // Flush the immediate callbacks that were scheduled during this batchresetRenderTimer(); flushSyncCallbackQueue(); }}}Copy the code

This method is relatively simple and simply calls the incoming callback function fn. And fn, is in legacyRenderSubtreeIntoContainer incoming

unbatchedUpdates(function () {
  updateContainer(children, fiberRoot, parentComponent, callback);
});
Copy the code

So let’s look at the updateContainer method again

updateContainer

Looking at the source code (source address) first, I will remove a lot of extraneous logic.

function updateContainer(element, container, parentComponent, callback) {
  // This current is the head node of the current 'Fiber' tree
  const current = container.current;
  // This is an event-related entry, so don't worry about it here
  var eventTime = requestEventTime();
  // This is a key entry. Lane represents the priority
  var lane = requestUpdateLane(current);
  // Create an update object with lane (priority) information. An update object means an update
  var update = createUpdate(eventTime, lane); 

  // Update payload corresponds to a React element
  update.payload = {
    element: element
  };

  // Handle the callback, which is actually the callback passed in when we call reactdom.render
  callback = callback === undefined ? null : callback;
  if(callback ! = =null) {{if (typeofcallback ! = ='function') {
        error('render(...) : Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback);
      }
    }
    update.callback = callback;
  }

  // Add the update to the team
  enqueueUpdate(current, update);
  / / scheduling fiberRoot
  scheduleUpdateOnFiber(current, lane, eventTime);
  // Returns the priority of the current node (fiberRoot)
  return lane;
}
Copy the code

The logic of this method is a bit complicated, but it can be broken down into three main points

  • 1. Request currentFiberThe node’slane(Priority)
  • 2. The combination oflane(Priority) to create the currentFiberThe node’supdateObject and enqueue it
  • 3. Schedule the current node (rootFiber) to update

However, since the first rendering link explained in this article is synchronous, the priority is not significant, so we can directly look at the method of scheduling nodes scheduleUpdateOnFiber.

scheduleUpdateOnFiber

This method is a bit long, so I just list the key logic (source address).

 // If the rendering is synchronous, this condition will be entered. If the mode is asynchronously rendered, it goes into its else logic
 // React uses fibre. mode to differentiate between different rendering modes
 if (lane === SyncLane) {
    if (
      // Check whether the unbatchedUpdates method is currently running(executionContext & LegacyUnbatchedContext) ! == NoContext &&// Check whether the current is render
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      schedulePendingInteractions(root, lane);

      // The key step to focus on starts with this method. Start render phase
      performSyncWorkOnRoot(root);
    } else {
      ensureRootIsScheduled(root, eventTime);
      schedulePendingInteractions(root, lane);
      if (executionContext === NoContext) {
        // Flush the synchronous work now, unless we're already working or inside
        // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
        // scheduleCallbackForFiber to preserve the ability to schedule a callback
        // without immediately flushing it. We only do this for user-initiated
        // updates, to preserve historical behavior of legacy mode.resetRenderTimer(); flushSyncCallbackQueue(); }}}Copy the code

In the previous steps, React created the basic entities in the Fiber tree, which are the fiberRoot nodes and the Current object underneath. In this method, we only need to focus on the performSyncWorkOnRoot method, from which we will proceed to the Render phase.

Render phase

The render phase builds and refines the Fiber tree, essentially iterating through fiberRoot and its current object as the top nodes to build the Fiber tree of their child elements.

Let’s start with the performSyncWorkOnRoot method.

performSyncWorkOnRoot

PerformSyncWorkOnRoot source code address

Here we focus on two pieces of logic

exitStatus = renderRootSync(root, lanes); . commitRoot(root);Copy the code

The renderRootSync method marks the start of the Render phase, and the commitRoot below marks the start of the Commit phase. We entered the Render phase first, so let’s look at the flow in renderRootSync first.

RenderRootSync source address

There are two pieces of logic to look at in this method

prepareFreshStack(root, lanes); . workLoopSync();Copy the code

Let’s go through the prepareFreshStack process and wait for it to complete before entering the workLoopSync traversal process.

PrepareFreshStack source address

The purpose of prepareFreshStack is to reset a new stack environment, and we only need to focus on one logic

workInProgress = createWorkInProgress(root.current, null);
Copy the code

CreateWorkInProgress is an important method, so let’s look at it in detail.

createWorkInProgress

The simplified source code is as follows, the source address

// Current passes the rootFiber object in the existing tree structure
function createWorkInProgress(current, pendingProps) {
  var workInProgress = current.alternate;
  // reactdom.render triggers the first screen render to enter this logic
  if (workInProgress === null) {
    // This is the first point you need to focus on. WorkInProgress is the return value of createFiber
    workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;
    // This is the second point you need to pay attention to, workInProgress alternate will point to current
    workInProgress.alternate = current;
    // This is the third point you need to pay attention to, current alternate will in turn point to workInProgress
    current.alternate = workInProgress;
  } else {
    // Do not worry about the else logic here
  }

  // Omit much of the property handling logic for workInProgress objects below
  // Return to the workInProgress node
  return workInProgress;
}
Copy the code

A little bit of a caveat here, inputcurrentThat’s the one beforefiberRootThe object ofcurrentObject.

To summarize what the createWorkInProgress method does

  • 1. CallcreateFiber.workInProgressiscreateFiberMethod return value
  • The 2.workInProgressthealternateWill point tocurrent
  • The 3.currentthealternatePoint the other wayworkInProgress
  • 4. Finally return oneworkInProgressnode

The createFiber method here, as its name implies, is used to create a Fiber node. The input parameters are values of current, so the workInProgress node is actually a copy of the current node. The structure of the tree should look like this:

With the workInProgress tree vertex created, now run the second key logic, workLoopSync, from the renderRootSync method.

workLoopSync

This method is very simple, just a traversal function

function workLoopSync() {
  // If workInProgress is not empty
  while(workInProgress ! = =null) {
    // Execute the performUnitOfWork method on itperformUnitOfWork(workInProgress); }}Copy the code

The methods listed below are iterated over and over in workLoopSync, so before I parse the performUnitOfWork method and its submethods, I will summarize the entire traversal process and analyze the methods once I have a general understanding.

All workLoopSync does is iterate through the while loop to determine whether workInProgress is empty and execute the performUnitOfWork function on it if it isn’t. The performUnitOfWork function triggers a call to the beginWork, creating a new Fiber node. If the Fiber node created by beginWork is not empty, formUniofWork uses the new Fiber node to update the value of workInProgress in preparation for the next loop.

When workInProgress is empty, it means that the entire Fiber tree has been built.

During this process, each new Fiber node created is mounted as a descendant of the previous workInProgress tree. Let’s look at it step by step.

performUnitOfWork

The source address

  next = beginWork(current, unitOfWork, subtreeRenderLanes);
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
Copy the code

There are actually two processes in performUnitOfWork, one is the beginWork process (creating a new Fiber node), and another is the completeWork process (when the beginWork traverses the leaf node of the current branch, next === null, Run the completeWork process) to handle the Fiber node to DOM node mapping logic.

Let’s start with the beginWork process

beginWork

The beginWork code is more than 400 lines, which is way too much, just to take some key logic. The source address

function beginWork(current, workInProgress, renderLanes) {...// If the current node is not empty, an identification is added to see if there is any update logic to process
  if(current ! = =null) {
    // Get old and new props
    var oldProps = current.memoizedProps;
    var newProps = workInProgress.pendingProps;

    // If the props are updated or the context changes, "accept the update" is considered necessary.
    if(oldProps ! == newProps || hasContextChanged() || ( workInProgress.type ! == current.type )) {// Put an update mark
      didReceiveUpdate = true;
    } else if (xxx) {
      // Do not need to update the case A
      return A
    } else {
      if(Case B) {didReceiveUpdate =true;
      } else {
        // Other cases where no updates are required, here our first rendering will execute the logic to this line
        didReceiveUpdate = false; }}}else {
    didReceiveUpdate = false; }...// This batch of switch is the core logic of beginWork, the original code is quite large
  switch (workInProgress.tag) {
    ......
    // Omit a lot of logic in the form of "case: XXX"
    // The root node will enter this logic
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes)
    // The node corresponding to the DOM tag will enter this logic
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes)

    // The text node will enter this logic
    case HostText:
      return updateHostText(current, workInProgress)
    ...... 
    // Omit a lot of logic in the form of "case: XXX"}}Copy the code

The core logic of beginWork is to call different node creation functions based on the different tag attributes of fiber nodes (nodes under the workInProgress tree) (which represent the type of tag that fiber currently belongs to).

These node-creation functions all end up generating children of the current node by calling the reconcileChildren method.

ReconcileChildren (beginWork Process)

This method is also relatively simple

function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  // Check whether current is null
  if (current === null) {
    // If current is null, the logic of mountChildFibers is entered
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  } else {
    // If current is not null, it enters the logic of reconcileChildFibersworkInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes); }}Copy the code

In the above two methods, we can also find where to assign values

var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);
Copy the code

Both methods are created using the ChildReconciler method, with different input parameters

ChildReconciler (beginWork process)

ChildReconciler code volume is also very large, code will not put, source address.

This method contains many functions for creating, adding, deleting, and modifying Fiber nodes for other functions to call. The return value is a function called reconcileChildFibers, which is a logical distributor that performs different Fiber node operations, depending on the reconcileChildFibers, and ultimately returns different target Fiber nodes.

The reconcileChildFibers and mountChildFibers differ in how they are dealt with, according to the reconcileChildFibers and mountChildFibers. ShouldTrackSideEffects should be true to add a flags attribute to the newly created Fiber node (before version 17, this attribute was effectTag) and assign a constant.

In the case of the root node, a Placement constant is assigned, which is a binary constant that tells the renderer, when rendering the real DOM, that the new DOM node is needed to process the Fiber node. There are many other constants of this type, source code addresses.

Here is a demo that will be used for the rest of the compilation.

function App() {
    return (
      <div className="App">
        <div className="container">
          <h1>I am heading</h1>
          <p>I'm the first paragraph</p>
          <p>I'm the second paragraph</p>
        </div>
      </div>
    );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Copy the code

Going back to the render link that we just had, because this cycle is the first to deal with the top nodes of the Current tree and the workInProgress tree, the Current exists and goes into the reconcileChildFibers method, which allows side effects to be tracked. Since the current workInProgress is the top node, it does not have an exact ReactElement mapping to it, so it will be the parent node of the root component in JSX, that is, the parent node of the App component. FiberNode is then created based on the App component’s ReactElement(jSX-compiled virtual DOM) object information, marked with Placement side effects, and returned to WorkinProgress.Child.

So it will beJSXThe root of the componentFiberWith the one created earlierFiberTree vertices are associated, as shown below.

The first loop is completed, and the workInProgress returned by the beginWork is not null because the App has child elements (workInProgress is actually the fiber node after compiling these JSX nodes), and the workLoopSync loop will continue. The final tree structure is as follows

Let’s take a look at these labelsfiberNode object.

The image above shows the App node, the two div child nodes, and the p tag node. As you can see, each non-text ReactElement has its corresponding Fiber node.

These nodes are all connected with each other. They establish relationships through the three attributes child, return and Sibling, among which child and return record the parent-child node relationship. Sibling refers to the first sibling of the current node.

See the following figure for details:

This is the final form of the workInProgress Fiber tree. As you can see, while the product in question is still conventionally referred to as a Fiber tree, the nature of its data structure has changed from a tree to a linked list.

Let’s look at another completeWork process.

CompleteUnitOfWork (completeWork process)

As mentioned above, in performUnitOfWork, after the beginWork process traverses to the leaf node, next will become null. When the next beginWork process ends, the corresponding completeWork process enters.

Rereference the code in the performUnitOfWork method used above

  next = beginWork(current, unitOfWork, subtreeRenderLanes);
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
Copy the code

CompleteUnitOfWork is a method that iterates through the loop, and it iterates through the following things in the loop

  • 1. CallcompleteWorkmethods
  • 2. Add the current node’s side effect chain (EffectList) inserted into its parent’s side effect chain (EffectList)
  • 3. Start from the current node and iterate through its siblings and parent nodes. When traversed to the sibling node, willreturnDrop the current call, triggering the sibling node correspondingperformUnitOfWorkLogic; When the parent node is traversed, it goes directly to the next loop, which repeats the logic of 1 and 2

Let’s start with the completeWork method.

CompleteWork (completeWork process)

CompleteWork source code address

CompleteWork is also a big function, and we only pull out the key logic

function completeWork(current, workInProgress, renderLanes) {
  // Get the properties of the Fiber node and store them in newProps
  var newProps = workInProgress.pendingProps;

  // Depending on the tag attribute of the workInProgress node, decide which logic to enter
  switch (workInProgress.tag) {
    ......
    // The h1 node type belongs to HostComponent, so this is the logic here
    case HostComponent:
      {
        popHostContext(workInProgress);
        var rootContainerInstance = getRootHostContainer();
        var type = workInProgress.type;
        // Check whether the current node exists. The current node does not exist because it is currently mounted
        if(current ! = =null&& workInProgress.stateNode ! =null) {
          updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
          if (current.ref !== workInProgress.ref) {
            markRef(workInProgress);
          }
        } else {
          // Return for exceptions.// Now we are ready to create the DOM node
          var currentHostContext = getHostContext();
          // _wasphase is a server-side rendering - dependent value, which is not a concern here
          var _wasHydrated = popHydrationState(workInProgress);

          // Determine if it is server render
          if (_wasHydrated) {
           ......
          } else {
            // This step is crucial. CreateInstance creates a DOM node
            var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
            // appendAllChildren attempts to mount the DOM node created in the previous step into the DOM tree
            appendAllChildren(instance, workInProgress, false.false);
            // stateNode is used to store the DOM node corresponding to the current Fiber node
            workInProgress.stateNode = instance; 

            FinalizeInitialChildren is used to set attributes for DOM nodes
            if(finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) { markUpdate(workInProgress); }}... }return null;
      }
    case HostText:
      {
        ......
      }
    case SuspenseComponent:
      {
        ......
      }
    case HostPortal:
      ......
      return null;
    case ContextProvider:
      ......
      return null; . }}Copy the code

So the first thing we need to know is going into thiscompleteWorkWhat are the parameters of phi that we know only whenbeginWorkYou don’t enter until you’ve traversed the first leaf nodecompleteWorkMethods. So, when you first run it, the arguments are actually in the demoh1Tag corresponding tofiberNode object. This is alsocompleteWorkOne of the features is that it runs strictly from the bottom up.

Then let’s look at some of the functional points of the completeWork method

  • 1.completeWorkThe core logic of the book is a huge piece ofswitchStatement, in this paragraphswitchStatement,completeWorkBased on theworkInProgressThe node’stagDifferent attributes, into differentDOMNode creation and processing logic.
  • In 2.DemoIn the sample,h1The node’stagThe type of the property should beHostComponentThat is, nativeDOMElement type.
  • 3.completeWorkIn thecurrent,workInProgressThat’s what I said beforecurrentTrees andworkInProgressNodes on the tree.

The workInProgress tree represents the “currently render tree” and the current tree represents the “existing tree”.

The workInProgress and current nodes are connected with alternate properties. During the component mount phase, the Current tree has only one top node and nothing else. Therefore, the current node corresponding to the h1 workInProgress node is null.

With that premise in mind, let’s look at the completeWork method again, and we can conclude that

CompleteWork handles the mapping logic from Fiber nodes to DOM nodes. Through three steps

  • 1. CreateDOMNode (CreateInstance)
  • 2.DOMNodes are inserted into the DOM tree (AppendAllChildren), assign toworkInProgressThe node’sstateNodeProperties (In addition, when the current node runs AppendAllChildren, it looks up its descendant sub-fiber nodes one by one and attaches the corresponding DOM node to the DOM node corresponding to its parent Fiber node. Therefore, the stateNode attribute in the highest level node, It’s a whole DOM tree)
  • 3. ToDOMNode Settings properties (FinalizeInitialChildren)

Step 2,3 (completeUnitOfWork process)

Let’s look at the code implementation of step 3

Starting with the current node, loops through its siblings and their parents. When traversing the sibling node, return the current call and trigger the performUnitOfWork logic corresponding to the sibling node. When the parent node is traversed, it goes directly to the next loop, which repeats the logic of 1 and 2

do{...The logic of steps 1 and 2 is omitted here

  // Get the sibling of the current node
  var siblingFiber = completedWork.sibling;

  // If sibling nodes exist
  if(siblingFiber ! = =null) {
    // Assign workInProgress to a sibling of the current node
    workInProgress = siblingFiber;
    // Return the completeUnitOfWork logic in progress
    return;
  } 

  // If the sibling does not exist, completeWork is assigned returnFiber, which is the parent of the current node
  completedWork = returnFiber; 
    // This step is complementary to the previous step, and the context requires that the workInProgress be consistent with completedWork
  workInProgress = completedWork;
} while(completedWork ! = =null);
Copy the code

The function is relatively simple, according to the demo, because the beginWork process is a depth-first traversal. When traversing the H1 tag, the traversal is interrupted and the completedWork process starts to be executed. H1 sibling p tag, actually the beginWork process has not run, so you need to call performUnitOfWork logic again.

Let’s talk about step two

Inserts the current node’s EffectList into its parent node’s EffectList.

The goal of this step is to identify the updates in the interface that need to be handled. Because in the actual operation, not all nodes will have updates that need to be processed. For example, in the mount phase, after the whole workInProgress tree is recursed, React will find that only one App node needs to be mounted. In the renewal phase, this phenomenon is more obvious.

How can the renderer quickly and well locate the nodes that really need to be updated? That’s what a side effectList does.

Each Fiber node maintains its own effectList in the form of a linked list of data structures, each element of which is a Fiber node. These Fiber nodes need to satisfy two commonalities:

  • Is the currentFiberDescendants of a node (not its own updates, but its descendants that need to be updated)
  • All side effects to be dealt with

thiseffectListList inFiberThe node is throughfirstEffectandlastEffectTo maintain.firstEffectsaideffectListThe first node of, andlastEffectThe last node is recorded.

Because the completeWork is executed bottom-up, on the top node you get an effect Fiber that stores all of the current Fiber tree.

In the demo, only the top node has a side effect chain (the Fiber node of the App component), and no side effect chain exists for all child nodes within the App component. When rendering or updating for the first time, the renderer will only deal with the App Fiber node in the side effect chain (App, as the smallest updated component, already contains the DOM node of the inner child element). Of course, if there are other components referenced in the App, the fiber of the App component will also contain the side effect chain of that component.

The commit phase

Commit is called in performSyncWorkOnRoot, which is an absolutely synchronous process.

commitRoot(root);
Copy the code

The source address

In terms of process, COMMI is divided into three stages: before mutation, mutation and layout.

  • The before mutation stage, in which the DOM node has not been rendered to the interface, triggers getSnapshotBeforeUpdate and also handles the useEffect hook scheduling logic.

  • Mutation, this stage is responsible for rendering DOM nodes. During rendering, the effectList is iterated over, performing different DOM operations depending on flags (EffectTags).

  • Layout, which handles the finishing logic after the DOM is rendered. Such as call componentDidMount/componentDidUpdate, call useLayoutEffect hook function callback, etc. In addition to this, it points the fiberRoot current pointer to the workInProgress Fiber tree.

Thank you

If this article helped you, please give it a thumbs up. Thanks!