[React source code series] 2 - Node initialization

[React source code series] 2 - Node initialization

Previous link: React source code learning series 1 - configuration and operation

This article is mainly about frantically interrupting points and remembering new nouns (functions). It does not involve why (why it runs like this), but simply look at the questions of what (what is executed here).

  1. run()

    This piece of code is executed by babel, and the function is as follows:

    function run(transformFn, script) {
          
          
      var scriptEl = document.createElement("script");
      scriptEl.text = transformCode(transformFn, script);
      headEl.appendChild(scriptEl);
    }
    

    Among them, it transformFncomes from inside babel. Since I am learning React now, I don't spend much time on babel.

    scriptThe content is as follows:

    const container = document.getElementById("container");
    
    // Create a root.
    const root = ReactDOM.createRoot(container);
    
    // Initial render
    root.render(<h1>Hello World</h1>);
    

    headElIt is a variable inside babel. I guess this general execution logic is to append the scriptElgenerated result to this variable controlled by babel to complete the rendering.

  2. createRoot()

    If you want to append, you must first create an object.

    The interesting thing is that there createRoot()are calls here:

    function createRoot$1(container, options) {
          
          
      {
          
          
        if (!Internals.usingClientEntryPoint) {
          
          
          error(
            'You are importing createRoot from "react-dom" which is not supported. ' +
              'You should instead import it from "react-dom/client".'
          );
        }
      }
    
      return createRoot(container, options);
    }
    

    Then again true createRoot():

    function createRoot(container, options) {
          
          
      if (!isValidContainer(container)) {
          
          
        throw new Error(
          "createRoot(...): Target container is not a DOM element."
        );
      }
    
      warnIfReactDOMContainerInDEV(container);
      var isStrictMode = false;
      var concurrentUpdatesByDefaultOverride = false;
      var identifierPrefix = "";
      var onRecoverableError = defaultOnRecoverableError;
      var transitionCallbacks = null;
    
      if (options !== null && options !== undefined) {
          
          
        {
          
          
          // 省略一堆 warning 和 error
        }
    
        if (options.unstable_strictMode === true) {
          
          
          isStrictMode = true;
        }
    
        if (options.identifierPrefix !== undefined) {
          
          
          identifierPrefix = options.identifierPrefix;
        }
    
        if (options.onRecoverableError !== undefined) {
          
          
          onRecoverableError = options.onRecoverableError;
        }
    
        if (options.transitionCallbacks !== undefined) {
          
          
          transitionCallbacks = options.transitionCallbacks;
        }
      }
    
      var root = createContainer(
        container,
        ConcurrentRoot,
        false,
        null,
        isStrictMode,
        concurrentUpdatesByDefaultOverride,
        identifierPrefix,
        onRecoverableError
      );
      markContainerAsRoot(root.current, container);
      var rootContainerElement =
        container.nodeType === COMMENT_NODE ? container.parentNode : container;
      listenToAllSupportedEvents(rootContainerElement);
      return new ReactDOMRoot(root);
    }
    

    After some investigation, $the is officially meaningless, but the convention is that $the function followed by will return one .Observable [1]

  3. createContainer

    The execution result of this function is the test point:

    function createContainer(
      containerInfo,
      tag, // TODO: We can remove hydration-specific stuff from createContainer once
      // we delete legacy mode. The new root API uses createHydrationContainer.
      hydrate,
      hydrationCallbacks,
      isStrictMode,
      concurrentUpdatesByDefaultOverride,
      identifierPrefix,
      onRecoverableError,
      transitionCallbacks
    ) {
          
          
      return createFiberRoot(
        containerInfo,
        tag,
        hydrate,
        hydrationCallbacks,
        isStrictMode,
        concurrentUpdatesByDefaultOverride,
        identifierPrefix,
        onRecoverableError
      );
    }
    

    Yes, the legendary Fiber started here.

  4. createFiberRoot

    function createFiberRoot(
      containerInfo,
      tag,
      hydrate,
      hydrationCallbacks,
      isStrictMode,
      concurrentUpdatesByDefaultOverride, // TODO: We have several of these arguments that are conceptually part of the
      // host config, but because they are passed in at runtime, we have to thread
      // them through the root constructor. Perhaps we should put them all into a
      // single type, like a DynamicHostConfig that is defined by the renderer.
      identifierPrefix,
      onRecoverableError,
      transitionCallbacks
    ) {
          
          
      var root = new FiberRootNode(
        containerInfo,
        tag,
        hydrate,
        identifierPrefix,
        onRecoverableError
      );
      // stateNode is any.
    
      var uninitializedFiber = createHostRootFiber(tag, isStrictMode);
      root.current = uninitializedFiber;
      uninitializedFiber.stateNode = root;
    
      {
          
          
        var initialCache = createCache();
        retainCache(initialCache); // The pooledCache is a fresh cache instance that is used temporarily
        // for newly mounted boundaries during a render. In general, the
        // pooledCache is always cleared from the root at the end of a render:
        // it is either released when render commits, or moved to an Offscreen
        // component if rendering suspends. Because the lifetime of the pooled
        // cache is distinct from the main memoizedState.cache, it must be
        // retained separately.
    
        root.pooledCache = initialCache;
        retainCache(initialCache);
        var initialState = {
          
          
          element: null,
          cache: initialCache,
          transitions: null,
        };
        uninitializedFiber.memoizedState = initialState;
      }
    
      initializeUpdateQueue(uninitializedFiber);
      return root;
    }
    
  5. createHostRootFiber

    Here again the keyword: concurrent.

    function createHostRootFiber(
      tag,
      isStrictMode,
      concurrentUpdatesByDefaultOverride
    ) {
          
          
      var mode;
    
      if (tag === ConcurrentRoot) {
          
          
        mode = ConcurrentMode;
    
        if (isStrictMode === true) {
          
          
          mode |= StrictLegacyMode;
    
          {
          
          
            mode |= StrictEffectsMode;
          }
        }
      } else {
          
          
        mode = NoMode;
      }
    
      if (isDevToolsPresent) {
          
          
        // Always collect profile timings when DevTools are present.
        // This enables DevTools to start capturing timing at any point–
        // Without some nodes in the tree having empty base times.
        mode |= ProfileMode;
      }
    
      return createFiber(HostRoot, null, null, mode);
    }
    

    As you can see, createHostRootFiberthe biggest difference modefrom .

  6. Fiber Node

    There is nothing to say about the initialization of Fiber Node, but there is an interesting comment in the source code:

    Note: The following is done to avoid a v8 performance cliff.
    Initializing the fields below to smis and later updating them with
    double values will cause Fibers to end up having separate shapes.
    This behavior/bug has something to do with Object.preventExtension().
    Fortunately this only impacts DEV builds.
    Unfortunately it makes React unusably slow for some applications.
    To work around this, initialize the fields below with doubles.

    Learn more about this here:

    https://github.com/facebook/react/issues/14365

    https://bugs.chromium.org/p/v8/issues/detail?id=8538

    I plan to take a look at this issue and this raised bug later.

  7. markContainerAsRoot

    This code is simple:

    function markContainerAsRoot(hostRoot, node) {
          
          
      node[internalContainerInstanceKey] = hostRoot;
    }
    

    internalContainerInstanceKeyThe name should be a unique key to manage the current instance. Later, you can also find out how this key is generated and what is the relationship with each update.

  8. listenToAllSupportedEvents

    Event Handler will be disassembled separately here. It can be seen that React has also done some processing on event handling internally.

  9. new ReactDOMRoot(root)

    This is actually binding an internal node:

    function ReactDOMRoot(internalRoot) {
          
          
      this._internalRoot = internalRoot;
    }
    

    After that, this node is returned to the earliest append segment.

    At this point, a blank node (Node) has been generated, and the next step is to createElementcreate a new element and renderrender it through .

flowchat

Update the process tree:

1
2
3
createRoot$1(HTMLNode: container): ReactDOMRoot
createRoot(HTMLNode: container): ReactDOMRoot
createContainer(...args): FiberRoot
markContainerAsRoot(...args): void
listenToAllSupportedEvents(FiberRoot: root): void
createFiberRoot(...args): FiberRoot
createHostRootFiber(...args): Fiber
createFiber(...args): FiberNode
run
render
appendChild
retainCache
initializeUpdateQueue

Reference

1: What does $ sign at the end of function name indicate?

Guess you like

Origin blog.csdn.net/weixin_42938619/article/details/124032368