一、这一篇只解决一个问题
上一篇已经讲清楚,React 18 之后入口从:
ReactDOM.render(<App />, container)
变成了:
const root = createRoot(container)
root.render(<App />)
这不是简单的 API 改名,而是 React 把 Root 这个运行时对象显式暴露出来了。
那么这一篇只继续往下拆一个问题:
const root = createRoot(container)
这一行代码到底创建了什么?
也就是:
createRoot(container)
createContainer(...)
createFiberRoot(...)
createHostRootFiber(...)
initializeUpdateQueue(...)
new ReactDOMRoot(...)
目标是把 React 应用启动时的"根结构"讲清楚。
二、先看 createRoot 阶段不做什么
很多人会把 createRoot 和 root.render 混在一起理解。
但实际上:
const root = createRoot(container)
执行完之后,React 还没有开始渲染你的 App。
它不会执行:
App()
不会创建:
AppFiber
divFiber
spanFiber
也不会创建真实 DOM 子节点。
此时 React 只是做了一件事:
为这个 DOM container 创建一个 React 内部 Root。
更准确地说,是创建一套根级别的数据结构,让后续的更新可以挂在这个 Root 上。
所以 createRoot 是初始化阶段,不是渲染阶段。
三、从用户代码开始
用户入口代码通常是:
import { createRoot } from 'react-dom/client'
const container = document.getElementById('root')
const root = createRoot(container)
这里传入的 container 是一个真实 DOM 节点。
例如:
<div id="root"></div>
也就是说,createRoot 的参数不是 ReactElement,而是宿主环境容器。
在 React DOM 里,这个容器就是 DOM 节点。
在源码里,createRoot 首先会做一些合法性检查,例如 container 是否是合法 DOM 容器。
简化后可以理解为:
function createRoot(container, options) {
if (!isValidContainer(container)) {
throw new Error('Target container is not a DOM element.')
}
const root = createContainer(container, ConcurrentRoot, ...)
return new ReactDOMRoot(root)
}
这一段主线很清楚:
createRoot 本身不是直接创建 FiberNode 的地方。
它会继续调用 reconciler 层的 createContainer。
四、createRoot 属于 react-dom,createContainer 属于 reconciler
这里要先讲一个源码分层。
createRoot 在 react-dom 里。
它关心的是 DOM 容器和对外 API。
但是 FiberRootNode、HostRootFiber 这些核心结构,是 reconciler 创建的。
也就是说:
react-dom/client 负责入口 API
react-reconciler 负责创建 Root 和 Fiber
这个分层很重要。
React DOM 只是 React 的一个 renderer。
React Native 也可以使用 reconciler 创建自己的 Root。
所以 createRoot 会把 DOM container 传给 reconciler,让 reconciler 创建内部 root。
简化关系是:
createRoot(container)
进入:
createContainer(container, ConcurrentRoot, ...)
再进入:
createFiberRoot(containerInfo, tag, ...)
这里的 containerInfo 就是外部传入的 DOM container。
五、createContainer 的核心职责
createContainer 可以理解为创建 React 内部根容器的统一入口。
简化代码:
export function createContainer(
containerInfo,
tag,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onUncaughtError,
onCaughtError,
onRecoverableError,
transitionCallbacks,
) {
return createFiberRoot(
containerInfo,
tag,
hydrate,
initialChildren,
hydrationCallbacks,
isStrictMode,
identifierPrefix,
onUncaughtError,
onCaughtError,
onRecoverableError,
transitionCallbacks,
)
}
具体版本里参数会有差异,但主线是稳定的。
createContainer 本质上是薄封装。
它最重要的动作是调用:
createFiberRoot(...)
createFiberRoot 才是真正创建 FiberRootNode 和 HostRootFiber 的地方。
六、ConcurrentRoot 这个参数的意义
在 createRoot 调用 createContainer 时,会传一个 root tag。
对于 React 18 之后的 createRoot,这个 tag 通常是:
ConcurrentRoot
这个值非常关键。
它说明这个 Root 是并发模式 Root。
也就是说,这个 Root 后续具备并发特性,例如:
可中断渲染
自动批处理
transition 更新
Suspense 并发行为
基于 lane 的优先级调度
如果是旧的 ReactDOM.render,对应的 Root 类型是 LegacyRoot。
所以入口 API 不只是创建方式不同,而是决定了这个 Root 的运行模式。
可以这样理解:
createRoot(container)
创建的是:
ConcurrentRoot
旧入口:
ReactDOM.render(element, container)
对应的是:
LegacyRoot
后面的调度行为、更新优先级处理、是否支持并发特性,都和这个 Root 类型有关。
所以 createRoot 阶段,React 已经确定了这个应用根的运行模式。
七、createFiberRoot:真正创建内部 Root
现在进入核心函数。
简化代码如下:
function createFiberRoot(
containerInfo,
tag,
hydrate,
initialChildren,
hydrationCallbacks,
isStrictMode,
identifierPrefix,
onUncaughtError,
onCaughtError,
onRecoverableError,
transitionCallbacks,
) {
const root = new FiberRootNode(
containerInfo,
tag,
hydrate,
identifierPrefix,
onUncaughtError,
onCaughtError,
onRecoverableError,
)
const uninitializedFiber = createHostRootFiber(tag, isStrictMode)
root.current = uninitializedFiber
uninitializedFiber.stateNode = root
initializeUpdateQueue(uninitializedFiber)
return root
}
这段就是 createRoot 阶段最核心的主线。
它创建了两个对象:
FiberRootNode
HostRootFiber
然后建立它们之间的双向关系:
root.current = uninitializedFiber
uninitializedFiber.stateNode = root
最后初始化 HostRootFiber 的 updateQueue。
这就是 createRoot 最重要的结果。
八、FiberRootNode 是什么
先看 FiberRootNode。
简化结构:
function FiberRootNode(containerInfo, tag, hydrate, identifierPrefix) {
this.tag = tag
this.containerInfo = containerInfo
this.current = null
this.pingCache = null
this.finishedWork = null
this.timeoutHandle = noTimeout
this.context = null
this.pendingContext = null
this.callbackNode = null
this.callbackPriority = NoLane
this.pendingLanes = NoLanes
this.suspendedLanes = NoLanes
this.pingedLanes = NoLanes
this.expiredLanes = NoLanes
this.finishedLanes = NoLanes
this.errorRecoveryDisabledLanes = NoLanes
this.entangledLanes = NoLanes
this.entanglements = createLaneMap(NoLanes)
this.identifierPrefix = identifierPrefix
}
字段很多,但不要逐个死记。
要按职责理解。
FiberRootNode 是 root 级状态容器。
它不代表某个组件。
它不代表某个 DOM 节点。
它代表整个 React 应用根。
它负责保存:
宿主容器
当前 Fiber 树
待处理优先级
调度回调
render 完成后的结果
Suspense 和 hydration 相关状态
错误恢复相关状态
九、containerInfo:连接宿主环境
this.containerInfo = containerInfo
在 React DOM 里,这个字段就是:
document.getElementById('root')
也就是你传给 createRoot 的 DOM 容器。
它的作用是告诉 React:
最终 DOM 子节点应该挂载到哪个宿主容器里。
注意,FiberRootNode 本身不是 DOM。
它只是保存了 DOM container 的引用。
后续 commit 阶段插入 DOM 时,需要通过 HostRootFiber 找到 FiberRootNode,再通过 FiberRootNode 找到 containerInfo。
也就是说,这条链路是:
HostRootFiber.stateNode
FiberRootNode.containerInfo
DOM container
这就是 Root 和真实 DOM 容器之间的连接方式。
十、current:当前已经生效的 Fiber 树
this.current = null
创建完 HostRootFiber 后,会赋值:
root.current = uninitializedFiber
这里的 current 是 React 双缓存模型里的 current tree 根节点。
它表示:
当前已经提交、生效在页面上的 Fiber 树。
在刚执行 createRoot 时,页面上还没有 React 渲染出来的内容。
但 React 仍然需要一个根 Fiber。
这个根 Fiber 就是 HostRootFiber。
所以此时:
fiberRoot.current = hostRootFiber
注意,这个 current 指向的是 Fiber 节点,不是 DOM。
FiberRootNode 是 root 管理对象。
HostRootFiber 才是 Fiber 树的根节点。
十一、pendingLanes:Root 上有哪些待处理更新
this.pendingLanes = NoLanes
刚 createRoot 时,pendingLanes 是空的。
因为还没有调用:
root.render(<App />)
也没有任何 setState。
当后续调用 root.render 或组件 setState 时,React 会给更新分配 lane,并把 lane 合并到 root.pendingLanes 上。
例如可以抽象理解为:
root.pendingLanes |= lane
这个字段告诉 React:
当前 root 上有哪些优先级的更新需要处理。
调度阶段会根据它选择下一批要处理的 lanes。
这也说明一个关键点:
调度不是直接看某个组件。
调度最终是看 Root。
组件上的更新会通过 Fiber 树冒泡到 Root,然后 Root 根据 pendingLanes 安排任务。
十二、callbackNode 和 callbackPriority:Root 的调度回调
this.callbackNode = null
this.callbackPriority = NoLane
这两个字段和调度有关。
当 Root 上有更新时,React 会通过 ensureRootIsScheduled 为这个 Root 安排任务。
如果已经有一个任务被安排了,React 不一定要重复安排。
它会比较当前已有 callback 的优先级和新的最高优先级。
例如:
if (existingCallbackPriority === newCallbackPriority) {
return
}
这就需要 root 保存:
当前已经安排的 callback 是什么
当前 callback 的优先级是什么
所以这两个字段是 Root 级别调度复用的基础。
刚 createRoot 时,它们为空,因为还没有任何更新。
十三、finishedWork:render 阶段完成后的 Fiber 树
this.finishedWork = null
后续 render 阶段完成后,React 会得到一棵完整的 workInProgress 树。
这棵树暂时挂在:
root.finishedWork
然后 commit 阶段会提交它。
commit 完成后,React 会把:
root.current
切换到这棵新的 finishedWork。
这个过程就是双缓存切换。
刚 createRoot 时,还没有 render,所以 finishedWork 是 null。
但是字段已经初始化好了。
十四、为什么 FiberRootNode 不是 Fiber
这里必须把概念区分清楚。
FiberRootNode 不是 Fiber。
它没有:
return
child
sibling
tag
pendingProps
memoizedProps
flags
这些 Fiber 节点字段。
它是整个 root 的管理对象。
真正的 Fiber 树根节点是 HostRootFiber。
所以不要把:
FiberRootNode
HostRootFiber
混成一个东西。
可以这样理解:
FiberRootNode 管理整个应用根
HostRootFiber 是 Fiber 树的根节点
它们之间通过两个字段互相连接:
fiberRoot.current = hostRootFiber
hostRootFiber.stateNode = fiberRoot
这组关系后面会频繁出现。
十五、createHostRootFiber:创建 Fiber 树的根节点
现在看第二个核心对象。
const uninitializedFiber = createHostRootFiber(tag, isStrictMode)
createHostRootFiber 会创建一个 tag 为 HostRoot 的 Fiber。
简化逻辑:
function createHostRootFiber(tag, isStrictMode) {
let mode
if (tag === ConcurrentRoot) {
mode = ConcurrentMode
if (isStrictMode === true) {
mode |= StrictLegacyMode | StrictEffectsMode
}
} else {
mode = NoMode
}
return createFiber(HostRoot, null, null, mode)
}
这段代码里有几个重点。
第一,HostRootFiber 的 tag 是 HostRoot。
第二,Root 的类型会影响 Fiber 的 mode。
第三,StrictMode 也会影响 mode。
第四,最终通过 createFiber 创建 FiberNode。
十六、HostRootFiber 的 tag
createFiber(HostRoot, null, null, mode)
这里的第一个参数是:
HostRoot
这说明这个 Fiber 是一棵 Fiber 树的根。
它不是用户写的组件。
不是 App。
不是 div。
它是 React 内部根 Fiber。
后续 render 阶段第一次 beginWork,处理的就是这个 HostRootFiber。
也就是说,React 的 Fiber 遍历不是从 AppFiber 开始的。
而是从 HostRootFiber 开始。
第一次用户传入的 <App />,后面会作为 HostRootFiber 的子节点被调和出来。
十七、HostRootFiber 的 pendingProps 为什么是 null
创建 HostRootFiber 时:
createFiber(HostRoot, null, null, mode)
第二个参数是 pendingProps。
这里是 null。
为什么?
因为 HostRootFiber 不是用户组件,它没有 JSX props。
它的数据来源不是 props,而是 updateQueue。
后续:
root.render(<App />)
传进来的 <App /> 会被包装成 update,放进 HostRootFiber.updateQueue。
render 阶段处理 HostRootFiber 时,会通过 processUpdateQueue 得到:
memoizedState.element
再拿这个 element 去调和子节点。
所以 HostRootFiber 和普通组件 Fiber 不一样。
普通 FunctionComponent 的输入来自 pendingProps。
HostRootFiber 的核心输入来自 updateQueue。
这是后面理解 updateHostRoot 的关键。
十八、HostRootFiber 的 stateNode 指回 FiberRootNode
创建完两个对象后,源码会建立连接:
root.current = uninitializedFiber
uninitializedFiber.stateNode = root
这两个赋值要一起看。
root.current = uninitializedFiber
表示:
FiberRootNode 当前生效的 Fiber 树根节点是 HostRootFiber。
uninitializedFiber.stateNode = root
表示:
HostRootFiber 对应的实例是 FiberRootNode。
为什么 HostRootFiber.stateNode 不是 DOM?
因为 HostRootFiber 对应的是 React Root,不是某个 DOM 元素。
对于不同 Fiber,stateNode 含义不同:
HostRootFiber.stateNode 是 FiberRootNode
HostComponentFiber.stateNode 是 DOM 节点
ClassComponentFiber.stateNode 是 class 实例
FunctionComponentFiber.stateNode 是 null
所以 stateNode 不能简单理解成 DOM。
它表示这个 Fiber 对应的运行时实例。
十九、initializeUpdateQueue:为什么 createRoot 就要初始化更新队列
createFiberRoot 里还有一个重要动作:
initializeUpdateQueue(uninitializedFiber)
也就是给 HostRootFiber 初始化 updateQueue。
简化结构:
function initializeUpdateQueue(fiber) {
const queue = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null
},
callbacks: null
}
fiber.updateQueue = queue
}
这一步非常关键。
因为下一步用户调用:
root.render(<App />)
React 会创建一个 update,然后把它放进 HostRootFiber.updateQueue。
如果 createRoot 阶段不初始化 updateQueue,root.render 阶段就没有地方存 update。
所以 createRoot 不只是创建 root,还提前为 root 更新准备好了队列。
二十、HostRootFiber 的 memoizedState
在 HostRootFiber 上,memoizedState 通常会保存 root 级别状态。
对于 HostRoot 来说,最关键的是 element。
可以简化理解为:
hostRootFiber.memoizedState = {
element: null
}
刚 createRoot 时,element 是 null。
因为还没有 render 任何东西。
当执行:
root.render(<App />)
之后,updateQueue 会被塞入一个 update:
update.payload = {
element: <App />
}
render 阶段处理 HostRootFiber 的 updateQueue 后,memoizedState 会变成:
hostRootFiber.memoizedState = {
element: <App />
}
然后 React 才会拿这个 element 去构建子 Fiber。
这说明 <App /> 不是 createRoot 阶段出现的。
它是在 root.render 之后才进入 HostRootFiber 的 updateQueue。
二十一、createRoot 结束后内部结构长什么样
执行完:
const root = createRoot(container)
后,内部大致是:
ReactDOMRoot {
_internalRoot: FiberRootNode
}
FiberRootNode:
{
tag: ConcurrentRoot,
containerInfo: container,
current: HostRootFiber,
pendingLanes: NoLanes,
callbackNode: null,
callbackPriority: NoLane,
finishedWork: null
}
HostRootFiber:
{
tag: HostRoot,
stateNode: FiberRootNode,
return: null,
child: null,
sibling: null,
pendingProps: null,
memoizedState: {
element: null
},
updateQueue: initializedQueue,
lanes: NoLanes,
childLanes: NoLanes,
flags: NoFlags,
alternate: null
}
此时最重要的状态是:
hostRootFiber.child === null
说明用户组件树还没出现。
hostRootFiber.updateQueue 已经存在
说明它已经准备好接收 root.render 的 update。
fiberRoot.pendingLanes === NoLanes
说明当前 root 还没有待处理更新。
fiberRoot.callbackNode === null
说明当前 root 还没有被调度。
这就是 createRoot 完成后的准确状态。
二十二、为什么 createRoot 不直接创建 workInProgress
createRoot 阶段只创建 current 树的根节点。
它不会创建 workInProgress 树。
workInProgress 是在真正进入 render 阶段时创建的。
原因是:
workInProgress 代表"一次正在进行的渲染工作"。
而 createRoot 只是创建根容器,还没有任何更新。
没有 update,就没有 render。
没有 render,就没有 workInProgress。
后续 root.render 之后,React 会调度 root。
等真正开始 render 时,才会执行类似:
createWorkInProgress(root.current, null)
基于 current HostRootFiber 创建 workInProgress HostRootFiber。
所以 createRoot 阶段只负责创建当前树的基础根节点。
它不负责创建本次渲染工作树。
二十三、为什么 createRoot 必须先创建 HostRootFiber
这个问题很重要。
如果还没有 App,为什么需要一个 HostRootFiber?
因为 root.render 本身就是一次更新。
而 update 必须挂在某个 Fiber 上。
第一次 render 时,AppFiber 还不存在。
所以只能先有一个内部根 Fiber 来承载这次更新。
这个 Fiber 就是 HostRootFiber。
调用:
root.render(<App />)
实际上会创建一个 update,并挂到:
hostRootFiber.updateQueue
如果没有 HostRootFiber,首次渲染的 update 就没有挂载位置。
所以 HostRootFiber 是整个更新系统的启动点。
它不是为了显示 UI。
它是为了让 root 级别更新进入 Fiber 更新队列。
二十四、createRoot 和后续 root.render 的边界
现在可以明确边界。
createRoot 做了这些事:
校验 container
创建 FiberRootNode
创建 HostRootFiber
连接 FiberRootNode 和 HostRootFiber
初始化 HostRootFiber.updateQueue
返回 ReactDOMRoot
createRoot 没有做这些事:
没有接收 ReactElement
没有创建 update
没有分配 lane
没有调度 root
没有执行组件函数
没有创建 AppFiber
没有操作 DOM 子节点
下一步 root.render 才会做:
接收 ReactElement
创建 update
把 element 放进 update.payload
把 update 放进 HostRootFiber.updateQueue
分配 lane
调度 root
这两个阶段必须分开理解。
二十五、这一篇的核心结论
createRoot(container) 的本质不是渲染,而是初始化 React Root。
它创建了用户层的 ReactDOMRoot,内部层的 FiberRootNode,以及 Fiber 树的根节点 HostRootFiber。
FiberRootNode 负责管理整个 root 的状态,包括 container、current、pendingLanes、callbackNode、finishedWork 等。
HostRootFiber 是 Fiber 树的根节点,它不是 App,也不是 DOM,而是 root 级更新的承载点。
两者通过下面关系连接:
fiberRoot.current = hostRootFiber
hostRootFiber.stateNode = fiberRoot
同时,React 会初始化 HostRootFiber 的 updateQueue,为后续 root.render(<App />) 提交 update 做准备。
所以执行完:
const root = createRoot(container)
之后,React 内部已经有了 root 结构,但还没有用户组件树。