经过五章的学习,我们终于回到了React应用的起点。

这一节我们完整的走通ReactDOM.render完成页面渲染的整个流程。

创建fiber

双缓存机制一节我们知道,首次执行ReactDOM.render会创建fiberRootNoderootFiber。其中fiberRootNode是整个应用的根节点,rootFiber是要渲染组件所在组件树的根节点

这一步发生在调用ReactDOM.render后进入的legacyRenderSubtreeIntoContainer方法中。

  1. // container指ReactDOM.render的第二个参数(即应用挂载的DOM节点)
  2. root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
  3. container,
  4. forceHydrate,
  5. );
  6. fiberRoot = root._internalRoot;

你可以从这里ReactDOM.render - 图1 (opens new window)看到这一步的代码

legacyCreateRootFromDOMContainer方法内部会调用createFiberRoot方法完成fiberRootNoderootFiber的创建以及关联。并初始化updateQueue

  1. export function createFiberRoot(
  2. containerInfo: any,
  3. tag: RootTag,
  4. hydrate: boolean,
  5. hydrationCallbacks: null | SuspenseHydrationCallbacks,
  6. ): FiberRoot {
  7. // 创建fiberRootNode
  8. const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
  9. // 创建rootFiber
  10. const uninitializedFiber = createHostRootFiber(tag);
  11. // 连接rootFiber与fiberRootNode
  12. root.current = uninitializedFiber;
  13. uninitializedFiber.stateNode = root;
  14. // 初始化updateQueue
  15. initializeUpdateQueue(uninitializedFiber);
  16. return root;
  17. }

根据以上代码,现在我们可以在双缓存机制一节基础上补充上rootFiberfiberRootNode的引用。

fiberRoot

你可以从这里ReactDOM.render - 图3 (opens new window)看到这一步的代码

创建update

我们已经做好了组件的初始化工作,接下来就等待创建Update来开启一次更新。

这一步发生在updateContainer方法中。

  1. export function updateContainer(
  2. element: ReactNodeList,
  3. container: OpaqueRoot,
  4. parentComponent: ?React$Component<any, any>,
  5. callback: ?Function,
  6. ): Lane {
  7. // ...省略与逻辑不相关代码
  8. // 创建update
  9. const update = createUpdate(eventTime, lane, suspenseConfig);
  10. // update.payload为需要挂载在根节点的组件
  11. update.payload = {element};
  12. // callback为ReactDOM.render的第三个参数 —— 回调函数
  13. callback = callback === undefined ? null : callback;
  14. if (callback !== null) {
  15. update.callback = callback;
  16. }
  17. // 将生成的update加入updateQueue
  18. enqueueUpdate(current, update);
  19. // 调度更新
  20. scheduleUpdateOnFiber(current, lane, eventTime);
  21. // ...省略与逻辑不相关代码
  22. }

你可以从这里ReactDOM.render - 图4 (opens new window)看到updateContainer的代码

值得注意的是其中update.payload = {element};

这就是我们在Update一节介绍的,对于HostRootpayloadReactDOM.render的第一个传参。

流程概览

至此,ReactDOM.render的流程就和我们已知的流程连接上了。

整个流程如下:

  1. 创建fiberRootNoderootFiberupdateQueue`legacyCreateRootFromDOMContainer`
  2. |
  3. |
  4. v
  5. 创建Update对象(`updateContainer`
  6. |
  7. |
  8. v
  9. fiberroot`markUpdateLaneFromFiberToRoot`
  10. |
  11. |
  12. v
  13. 调度更新(`ensureRootIsScheduled`
  14. |
  15. |
  16. v
  17. render阶段(`performSyncWorkOnRoot` `performConcurrentWorkOnRoot`
  18. |
  19. |
  20. v
  21. commit阶段(`commitRoot`

React的其他入口函数

当前React共有三种模式:

  • legacy,这是当前React使用的方式。当前没有计划删除本模式,但是这个模式可能不支持一些新功能。

  • blocking,开启部分concurrent模式特性的中间模式。目前正在实验中。作为迁移到concurrent模式的第一个步骤。

  • concurrent,面向未来的开发模式。我们之前讲的任务中断/任务优先级都是针对concurrent模式。

你可以从下表看出各种模式对特性的支持:

legacy 模式blocking 模式concurrent 模式
String RefsReactDOM.render - 图5 (opens new window)🚫🚫
Legacy ContextReactDOM.render - 图6 (opens new window)🚫🚫
findDOMNodeReactDOM.render - 图7 (opens new window)🚫🚫
SuspenseReactDOM.render - 图8 (opens new window)
SuspenseListReactDOM.render - 图9 (opens new window)🚫
Suspense SSR + Hydration🚫
Progressive Hydration🚫
Selective Hydration🚫🚫
Cooperative Multitasking🚫🚫
Automatic batching of multiple setStates🚫*
Priority-based RenderingReactDOM.render - 图10 (opens new window)🚫🚫
Interruptible PrerenderingReactDOM.render - 图11 (opens new window)🚫🚫
useTransitionReactDOM.render - 图12 (opens new window)🚫🚫
useDeferredValueReactDOM.render - 图13 (opens new window)🚫🚫
Suspense Reveal “Train”ReactDOM.render - 图14 (opens new window)🚫🚫

*:legacy模式在合成事件中有自动批处理的功能,但仅限于一个浏览器任务。非React事件想使用这个功能必须使用 unstable_batchedUpdates。在blocking模式和concurrent模式下,所有的setState在默认情况下都是批处理的。

**:会在开发中发出警告。

模式的变化影响整个应用的工作方式,所以无法只针对某个组件开启不同模式。

基于此原因,可以通过不同的入口函数开启不同模式:

  • legacyReactDOM.render(<App />, rootNode)
  • blockingReactDOM.createBlockingRoot(rootNode).render(<App />)
  • concurrentReactDOM.createRoot(rootNode).render(<App />)

你可以在这里ReactDOM.render - 图15 (opens new window)看到React团队解释为什么会有这么多模式

虽然不同模式的入口函数不同,但是他们仅对fiber.mode变量产生影响,对我们在流程概览中描述的流程并无影响。