react源码:应用入口:ReactDOM.render

522 阅读2分钟

点击这里进入react原理专栏

这篇文章来看一下react应用的入口:ReactDOM.render的流程。ReactDOM.render主要做了三件事:创建整个应用的根fiber节点:fiberRoot,合成事件的处理以及挂载应用。

render方法会调用legacyRenderSubtreeIntoContainer方法

function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
  var root = container._reactRootContainer;
  var fiberRoot;

  if (!root) {
    // 初次挂载,创建根节点
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
    fiberRoot = root._internalRoot;
    // 应用的挂载
    unbatchedUpdates(function () {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    // 。。。
  }
  return getPublicRootInstance(fiberRoot);
}

legacyCreateRootFromDOMContainer方法源码如下

function legacyCreateRootFromDOMContainer(container, forceHydrate) {
  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.');
        }
      }
      // 清空root节点内的dom元素
      container.removeChild(rootSibling);
    }
  }
  // 。。。
  return createLegacyRoot(container, shouldHydrate ? {
    hydrate: true
  } : undefined);
}

这个方法会清空root元素内的dom节点,之后经过一系列方法调用,来到了createRootImpl

function createRootImpl(container, tag, options) {
  // ...
  var root = createContainer(container, tag, hydrate);
  {
    var rootContainerElement = container.nodeType === COMMENT_NODE ? container.parentNode : container;
    listenToAllSupportedEvents(rootContainerElement);
  }
  // ...
  return root;
}

这个方法主要干了两件事:创建fiberRoot,以及合成事件的处理。分别对应createContainer方法和listenToAllSupportedEvents方法。合成事件的原理会有文章进行介绍。

至此,legacyCreateRootFromDOMContainer方法执行完毕,接下来开始挂载整个应用,进入updateContainer方法(unbatchedUpdates先略过)。updateContainer方法会调用scheduleUpdateOnFiber方法,开始进行更新(这个方法就是更新react应用的入口)。

下面看scheduleUpdateOnFiber方法的这段逻辑:

if (
(executionContext & LegacyUnbatchedContext) !== NoContext &&
(executionContext & (RenderContext | CommitContext)) === NoContext) {
  // ReactDOM.render进入这里
  schedulePendingInteractions(root, lane);
  performSyncWorkOnRoot(root);
} else {
  // 后续更新进入这里
  ensureRootIsScheduled(root, eventTime);
  schedulePendingInteractions(root, lane);

  if (executionContext === NoContext) {
    resetRenderTimer();
    flushSyncCallbackQueue();
  }
}

现在再看unbatchedUpdates方法

function unbatchedUpdates(fn, a) {
  var prevExecutionContext = executionContext;
  executionContext &= ~BatchedContext;
  executionContext |= LegacyUnbatchedContext;

  try {
    return fn(a);
  } finally {
    executionContext = prevExecutionContext;
    // ...
  }
}

unbatchedUpdates方法执行了executionContext |= LegacyUnbatchedContext,因此之后到unbatchedUpdates方法中,executionContext & LegacyUnbatchedContext) !== NoContexttrue,而RenderContextCommitContext时在render阶段和commit阶段被设置的,因此executionContext & (RenderContext | CommitContext)) === NoContexttrue。之后进入了performSyncWorkOnRoot(后续更新的过程也会调用performSyncWorkOnRoot,只是调用方式不同)。

进入performSyncWorkOnRoot中有两个重要的方法调用:renderRootSynccommitRoot,分别对应render阶段和commit阶段的入口。这些内容会有专门的文章进行讲解。

本文讲解了整个react的入口ReactDOM.render的部分流程,这里提到了合成事件,rendercommit阶段,这些内容都会有相应的文章进行讲解,敬请期待!!