[React 源码系列] 2 - 结点的初始化

[React 源码系列] 2 - 结点的初始化

上一篇链接:React 源码学习系列 1 - 配置及运行

这一篇主要就是疯狂地打断点和记新的名词(函数),并不会涉及到 why(为什么要这么跑),单纯的看看 what(什么东西在这里执行) 这样的问题。

  1. run()

    这一段代码是由 babel 执行的,函数如下:

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

    其中 transformFn 来源于 babel 内部,鉴于现在学习的是 React,就不多花时间在 babel 上了。

    script 的内容如下:

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

    headEl 是 babel 内部的一个变量,我猜测 这段大概的执行逻辑就是为了将 scriptEl 生成的结果 append 到由 babel 控制的这个变量中,从而完成渲染。

  2. createRoot()

    想要 append,自然要先创建对象才行。

    有意思的事情就是,这里其实有两个 createRoot() 被调用:

    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);
    }
    

    随后再是真正的 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);
    }
    

    经过一番调查后,函数名后跟 $ 的做法官方上定义是没有任何意义,但是约定俗成的做法是后跟 $ 的函数会返回一个 Observable [1]

  3. createContainer

    这个函数的执行结果就是考点了:

    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
      );
    }
    

    是的,传说中的 Fiber 就是从这里开始的。

  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

    这里再一次出现了关键字: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);
    }
    

    可以看到,createHostRootFiber 与普通的 FiberNode 最大的区别大概就在于初始化 mode 了。

  6. Fiber Node

    Fiber Node 的初始化倒是没什么好说的,但是源码这里有一段很有意思的注释:

    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

    之后打算看看这个 issue 和这个提出来的 bug。

  7. markContainerAsRoot

    这段代码很简单:

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

    internalContainerInstanceKey 看名字应该是管理当前 instance 的一个唯一 key,后面也可以找找看这个 key 是怎么生成的,和每次的 update 有什么关系。

  8. listenToAllSupportedEvents

    Event Handler 这里之后单独拆开来再说,可以看得出来 React 内部对 event handling 也做了一些处理。

  9. new ReactDOMRoot(root)

    这里其实就是绑定了一个内部的结点:

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

    后,将这个结点返回给了最早的 append 那一段。

    至此,一个空白的 结点(Node) 就生成了,下一步要做的就是通过 createElement 创建一个新的元素,再通过 render 进行渲染。

flowchat

更新一下流程树:

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?

猜你喜欢

转载自blog.csdn.net/weixin_42938619/article/details/124032368