ReactDOM.render
经过五章的学习,我们终于回到了React
应用的起点。
这一节我们完整的走通ReactDOM.render
完成页面渲染的整个流程。
创建fiber
从我们知道,首次执行ReactDOM.render
会创建fiberRootNode
和rootFiber
。其中fiberRootNode
是整个应用的根节点,rootFiber
是要渲染组件所在组件树的根节点
。
这一步发生在调用ReactDOM.render
后进入的legacyRenderSubtreeIntoContainer
方法中。
// container指ReactDOM.render的第二个参数(即应用挂载的DOM节点)
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
fiberRoot = root._internalRoot;
你可以从这里看到这一步的代码
legacyCreateRootFromDOMContainer
方法内部会调用createFiberRoot
方法完成fiberRootNode
和rootFiber
的创建以及关联。并初始化updateQueue
。
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
// 创建fiberRootNode
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// 创建rootFiber
const uninitializedFiber = createHostRootFiber(tag);
// 连接rootFiber与fiberRootNode
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
// 初始化updateQueue
initializeUpdateQueue(uninitializedFiber);
return root;
}
根据以上代码,现在我们可以在基础上补充上rootFiber
到fiberRootNode
的引用。
你可以从这里看到这一步的代码
创建update
我们已经做好了组件的初始化工作,接下来就等待创建Update
来开启一次更新。
这一步发生在updateContainer
方法中。
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
// ...省略与逻辑不相关代码
// 创建update
const update = createUpdate(eventTime, lane, suspenseConfig);
// update.payload为需要挂载在根节点的组件
update.payload = {element};
// callback为ReactDOM.render的第三个参数 —— 回调函数
callback = callback === undefined ? null : callback;
if (callback !== null) {
update.callback = callback;
}
// 将生成的update加入updateQueue
enqueueUpdate(current, update);
// 调度更新
scheduleUpdateOnFiber(current, lane, eventTime);
// ...省略与逻辑不相关代码
}
你可以从这里看到
updateContainer
的代码
值得注意的是其中update.payload = {element};
这就是我们在介绍的,对于HostRoot
,payload
为ReactDOM.render
的第一个传参。
流程概览
至此,ReactDOM.render
的流程就和我们已知的流程连接上了。
整个流程如下:
创建fiberRootNode、rootFiber、updateQueue(`legacyCreateRootFromDOMContainer`)
|
|
v
创建Update对象(`updateContainer`)
|
|
v
从fiber到root(`markUpdateLaneFromFiberToRoot`)
|
|
v
调度更新(`ensureRootIsScheduled`)
|
|
v
render阶段(`performSyncWorkOnRoot` 或 `performConcurrentWorkOnRoot`)
|
|
v
commit阶段(`commitRoot`)
React的其他入口函数
当前React
共有三种模式:
legacy
,这是当前React
使用的方式。当前没有计划删除本模式,但是这个模式可能不支持一些新功能。blocking
,开启部分concurrent
模式特性的中间模式。目前正在实验中。作为迁移到concurrent
模式的第一个步骤。concurrent
,面向未来的开发模式。我们之前讲的任务中断/任务优先级
都是针对concurrent
模式。
你可以从下表看出各种模式对特性的支持:
legacy 模式 | blocking 模式 | concurrent 模式 | |
---|---|---|---|
String Refs | ✅ | ** | ** |
Legacy Context | ✅ | ** | ** |
findDOMNode | ✅ | ** | ** |
Suspense | ✅ | ✅ | ✅ |
SuspenseList | | ✅ | ✅ |
Suspense SSR + Hydration | | ✅ | ✅ |
Progressive Hydration | | ✅ | ✅ |
Selective Hydration | | | ✅ |
Cooperative Multitasking | | | ✅ |
Automatic batching of multiple setStates | * | ✅ | ✅ |
Priority-based Rendering | | | ✅ |
Interruptible Prerendering | | | ✅ |
useTransition | | | ✅ |
useDeferredValue | | | ✅ |
Suspense Reveal "Train" | | | ✅ |
*:legacy
模式在合成事件中有自动批处理的功能,但仅限于一个浏览器任务。非React
事件想使用这个功能必须使用 unstable_batchedUpdates
。在blocking
模式和concurrent
模式下,所有的setState
在默认情况下都是批处理的。
**:会在开发中发出警告。
模式的变化影响整个应用的工作方式,所以无法只针对某个组件开启不同模式。
基于此原因,可以通过不同的入口函数
开启不同模式:
legacy
--ReactDOM.render(<App />, rootNode)
blocking
--ReactDOM.createBlockingRoot(rootNode).render(<App />)
concurrent
--ReactDOM.createRoot(rootNode).render(<App />)
你可以在这里看到
React
团队解释为什么会有这么多模式
虽然不同模式的入口函数
不同,但是他们仅对fiber.mode
变量产生影响,对我们在中描述的流程并无影响。