React 源码讲解第 2 节-入口 API 之 ReactDOM.render

前言

在 node_modules 目录中找到 react-dom 包,打开 cjs 文件夹。
打开 react-dom.development.js 文件。

定位到 25005 行,找到导出的 render 方法,源码如下。

exports.render = render;

找到 render 的实现方法,源码如下。

function render(element, container, callback) {
    
    
  if (!isValidContainer(container)) {
    
    
    {
    
    
      throw Error( "Target container is not a DOM element." );
    }
  }

  {
    
    
    var isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined;

    if (isModernRoot) {
    
    
      error('You are calling ReactDOM.render() on a container that was previously ' + 'passed to ReactDOM.createRoot(). This is not supported. ' + 'Did you mean to call root.render(element)?');
    }
  }

  return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}

源码分析

1. render 参数

通过 demo 中的 ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ),结合源码,可以看出 render 方法中对应的参数。
第一个参数 element 指的就是 <App />
第二个参数 container 指的就是根节点 document.getElementById('root')
第三个参数 callback 很少用到,这里不展开。

2. 判断是否为有效节点

isValidContainer(container) 是判断父节点 container 是否为有效节点,以下任意节点都是有效节点。

var ELEMENT_NODE = 1;//元素节点
var TEXT_NODE = 3;//文本节点
var COMMENT_NODE = 8;//注释节点
var DOCUMENT_NODE = 9;//文档,DOM树根节点
var DOCUMENT_FRAGMENT_NODE = 11;//节点片段
3. 判断是否为根节点

isContainerMarkedAsRoot 判断是否为根节点。

4. 初始化根节点(创建 FiberRoot 及 RootFiber)

4.1 找到 legacyRenderSubtreeIntoContainer 的实现方法,源码如下。

function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
    
    
  {
    
    
    topLevelUpdateWarnings(container);
    warnOnInvalidCallback$1(callback === undefined ? null : callback, 'render');
  } // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  // member of intersection type." Whyyyyyy.


  var root = container._reactRootContainer;
  var fiberRoot;

  if (!root) {
    
    
    // Initial mount
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
    fiberRoot = root._internalRoot;

    if (typeof callback === 'function') {
    
    
      var originalCallback = callback;

      callback = function () {
    
    
        var instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    } // Initial mount should not be batched.


    unbatchedUpdates(function () {
    
    
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    
    
    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);
}

通过以上代码可以看到该方法中的参数如下。
第一个参数 parentComponent 为 null。
第二个参数 children 指的就是 <App />
第三个参数 container 指的就是根节点 document.getElementById('root')
第四个参数 forceHydrate 判断是否为服务端渲染,默认为 false。
第五个参数 callback 回调函数。

首次加载时,root 肯定为 underfined。所以 root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);

4.2 找到 legacyCreateRootFromDOMContainer(container, forceHydrate) 实现方法,源码如下。

function legacyCreateRootFromDOMContainer(container, forceHydrate) {
    
    
  var shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // First clear any existing content.

  if (!shouldHydrate) {
    
    
    var warned = false;
    var rootSibling;

    while (rootSibling = container.lastChild) {
    
    
      {
    
    
        if (!warned && rootSibling.nodeType === ELEMENT_NODE && rootSibling.hasAttribute(ROOT_ATTRIBUTE_NAME)) {
    
    
          warned = true;

          error('render(): Target node has markup rendered by React, but there ' + 'are unrelated nodes as well. This is most commonly caused by ' + 'white-space inserted around server-rendered markup.');
        }
      }

      container.removeChild(rootSibling);
    }
  }

  {
    
    
    if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
    
    
      warnedAboutHydrateAPI = true;

      warn('render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + 'will stop working in React v17. Replace the ReactDOM.render() call ' + 'with ReactDOM.hydrate() if you want React to attach to the server HTML.');
    }
  }

  return createLegacyRoot(container, shouldHydrate ? {
    
    
    hydrate: true
  } : undefined);
}

参数 container 为根节点, forceHydrate 为 false。

看核心代码 createLegacyRoot(container, shouldHydrate ? { hydrate: true } : undefined); 其内部实例化了 ReactDOMBlockingRoot(container, LegacyRoot, options)。该实例的属性 _internalRoot 等于 createRootImpl(container, tag, options)。可以看到最后是实例化了 FiberRootNode(containerInfo, tag, hydrate) 构造函数,也就是 fiberRoot 。

5. 创建 expirationTime 和 update

回到 updateContainer(children, fiberRoot, parentComponent, callback) 方法。源码如下。

function updateContainer(element, container, parentComponent, callback) {
    
    
  {
    
    
    onScheduleRoot(container, element);
  }

  var current$1 = container.current;
  var currentTime = requestCurrentTimeForUpdate();

  {
    
    
    // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
    if ('undefined' !== typeof jest) {
    
    
      warnIfUnmockedScheduler(current$1);
      warnIfNotScopedWithMatchingAct(current$1);
    }
  }

  var suspenseConfig = requestCurrentSuspenseConfig();
  var expirationTime = computeExpirationForFiber(currentTime, current$1, suspenseConfig);
  var context = getContextForSubtree(parentComponent);

  if (container.context === null) {
    
    
    container.context = context;
  } else {
    
    
    container.pendingContext = context;
  }

  {
    
    
    if (isRendering && current !== null && !didWarnAboutNestedUpdates) {
    
    
      didWarnAboutNestedUpdates = true;

      error('Render methods should be a pure function of props and state; ' + 'triggering nested component updates from render is not allowed. ' + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' + 'Check the render method of %s.', getComponentName(current.type) || 'Unknown');
    }
  }

  var update = createUpdate(expirationTime, suspenseConfig); // Caution: React DevTools currently depends on this property
  // being called "element".

  update.payload = {
    
    
    element: element
  };
  callback = callback === undefined ? null : callback;

  if (callback !== null) {
    
    
    {
    
    
      if (typeof callback !== 'function') {
    
    
        error('render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback);
      }
    }

    update.callback = callback;
  }

  enqueueUpdate(current$1, update);
  scheduleWork(current$1, expirationTime);
  return expirationTime;
}

该方法中创建了一个更新。var update = createUpdate(expirationTime, suspenseConfig)
然后将更新加入了任务列队。enqueueUpdate(current$1, update)
将当前节点和过期时间加入调度器中。 scheduleWork(current$1, expirationTime)

总结

  1. 在 render 方法的第一个参数 element 的基础上创建 fiberRoot 节点。
  2. 通过 expirationTime 过期时间创建一个更新 createUpdate(expirationTime, suspenseConfig)
  3. 将当前元素 current$1 和更新 update 作为参数加入更新队列 enqueueUpdate(current$1, update)
  4. 将当前元素 current$1 和过期时间 expirationTime 作为参数进入调度器 scheduleWork(current$1, expirationTime)

猜你喜欢

转载自blog.csdn.net/weixin_44135121/article/details/108575542