ReactDOM.render Source Parsing-1

Looking at the source code of the react-dom package, we find that it is much more complex than the react package. There is no cross-package call in the react package. What he does is to define the ReactElement object and encapsulate the basic operation of ReactElement, while the react-dom package has complex function calls. This article will do a preliminary analysis of ReactDOM.render source code.
If there are any inappropriate points in the article, you are welcome to exchange your opinions. Reaction version 16.8.2. Annotations added to the source code in github react-source-learn The branch of learn s.

Preface

When using react, you often write code similar to the following:

import ReactDOM from 'react-dom';

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);

Code 1

The imported ReactDOM here is the object exported in packages/react-dom/client/ReactDOM.js. from File It can be seen that ReactDOM objects have the following methods: (ps: There are actually many other methods from the source code)

  • render()
  • hydrate()
  • unmountComponentAtNode()
  • findDOMNode()
  • createPortal()

This article only introduces render() method

code analysis

The render method is defined as follows:

   render(
    element: React$Element<any>,
    container: DOMContainer,
    callback: ?Function,
  ) {
    invariant(
      // 1
      isValidContainer(container),
      'Target container is not a DOM element.',
    );
    if (__DEV__) {
      warningWithoutStack(
        !container._reactHasBeenPassedToCreateRootDEV,
        'You are calling ReactDOM.render() on a container that was previously ' +
          'passed to ReactDOM.%s(). This is not supported. ' +
          'Did you mean to call root.render(element)?',
        enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
      );
    }
    // 2
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback,
    );
  },

Code 2

The render method receives two required parameters, one optional parameter. Combining with the call of code 1, it can be seen that element is a ReactElement object, container is a dom node, and callback is not specified in code 1 above. It is an optional function.

The render method does a relatively simple job of checking the container parameter and calling the legacyRenderSubtreeIntoContainer method and returning it.

Next comes legacy RenderSubtree Into Container

// Deleted the branch that ReactDOM.render will not walk on the first call
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>, // ReactDOM.render is null
  children: ReactNodeList, // Is a ReactElement, ReactDOM. render is the first parameter
  container: DOMContainer, // Is a dom node, ReactDOM.render is the second parameter
  forceHydrate: boolean, // ReactDOM.render is false
  callback: ?Function, // ReactDOM.render is the third parameter
) {
  if (__DEV__) {
    topLevelUpdateWarnings(container);
  }

  // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  // member of intersection type." Whyyyyyy.
  // According to type, Root type is an object that contains
  // render method
  // unmount method
  // legacy_renderSubtreeIntoContainer method
  // createBatch method
  // _ InternaalRoot attribute
  let root: Root = (container._reactRootContainer: any);
  if (!root) { // This is where ReactDOM.render is called
      // Initial mount
      // Call legacy CreateRootFromDOMContainer to get Root
      root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate, // ReactDOM.render is false
    );

    // Add parameters to callback
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(root._internalRoot);
        originalCallback.call(instance);
      };
    }
    // Initial mount should not be batched.
    // This is the method in packages/react-reconciler/ReactFiberScheduler.js
    // TOLEARN: This should be some scheduling process. Look at it later.
    unbatchedUpdates(() => {
      if (parentComponent != null) {
        root.legacy_renderSubtreeIntoContainer(
          parentComponent,
          children,
          callback,
        );
      } else { // ReactDOM.render method goes here
        // Here root.render returns something called Work, TOLEARN, which is explained later.
        root.render(children, callback);
      }
    });
  }
  // getPublicRootInstance is the method in / packages/react-reconciler/ReactFiberReconciler.js
  // As for what he's returning, let's see later. In a word, he's a parameter of the callback function of my ReactDOM.render method.
  // Also return value
  return getPublicRootInstance(root._internalRoot);
}

The legacy RenderSubtree Into Container did the following at the first render:

  1. Call legacy CreateRootFromDOMContainer to get an instance of ReactRoot
  2. Inject a parameter instance into callback
  3. Call unbatchedUpdates to start a round of scheduling, which is guessed
  4. Return instance

Getting instance is called getPublicRootInstance, getPublicRootInstance is the method in / packages/react-reconciler/ReactFiberReconciler.js, which will be studied later.

For unbatched Updates, this is the method in packages/react-reconciler/ReactFiberScheduler.js. It's not quite clear after a brief look.

Next, take a look at the acquisition of ReactRoot instances

// Delete the _DEV_ branch code
// If you need to clean up the child nodes of container, clean up, and then new ReactRoot and return
function legacyCreateRootFromDOMContainer(
  container: DOMContainer,  // dom node
  forceHydrate: boolean, // false render
): Root {
  // Whether you don't need to clean up the child elements of container, the first render is false, that is, you need to clean up
  const shouldHydrate =
    forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // The first call was false
  // First clear any existing content.
  if (!shouldHydrate) { // The first render came here
    let warned = false;
    let rootSibling;
    // Here we've cleaned out all the child elements of container.
    while ((rootSibling = container.lastChild)) {
      container.removeChild(rootSibling);
    }
  }
  // Legacy roots are not async by default.
  const isConcurrent = false;
  return new ReactRoot(container, isConcurrent, shouldHydrate);
}

This method is relatively simple. When ReactDOM.render is called for the first time, shouldHydrate will be false, so it will go to if (!shouldHydrate) branch, clean up all the child nodes of the container node, and finally a new ReactRoot will be used as the return value. The content about ReactRoot will be put in the following article analysis.

Summary

The above is a function call diagram of ReactDOM.render.

  • First check the validity of the container parameter in the render method, and then call legacyRenderSubtreeIntoContainer
  • In legacy RenderSubtreeInto Container, calling legacy CreateRootFromDOMContainer gets an instance of ReactRoot, calling getPublicRootInstance gets an instance for injection into callback, and as a return value, calling unbatchedUpdates to start the scheduling process
  • In legacy CreateRootFromDOMContainer, all the child nodes in the container are cleaned up first, and then a new ReactRoot is returned.

TODO

  • How unbatched updates are scheduled
  • What kind of class is ReactRoot?
  • Where is the operation of dom

Keywords: Javascript React github Attribute

Added by turek on Sat, 18 May 2019 20:21:19 +0300