🧑💻 前言
在上一篇文章里,我们从 scheduleUpdateOnFiber
出发,跟踪到 React 内部如何触发 调度更新。
调度完成后,更新会进入 render 阶段。
render 阶段的核心目标是:基于最新的 props、state,构建一棵全新的 Fiber 树,并在完成阶段生成 DOM 节点。
本篇文章我们将从 首次挂载流程 出发,结合源码逐步拆解,理解 React 在没有 Diff 复用的情况下,如何一步步"生造"出一棵 Fiber 树并对应的 DOM。
1. Render 阶段全景
先整体感受一下 render 阶段的 流程图:
ts
Render 流程:
│
├─ 初始化阶段
│ ├─ ReactDOM.render(<App />, container)
│ ├─ 创建 FiberRoot 与 HostRootFiber
│ └─ scheduleUpdateOnFiber(HostRootFiber)
│
├─ 递阶段(beginWork)
│ ├─ performUnitOfWork(workInProgress)
│ ├─ beginWork(current, workInProgress)
│ │ ├─ HostRoot → updateHostRoot()
│ │ ├─ ClassComponent → constructClassInstance()
│ │ ├─ FunctionComponent → renderWithHooks()
│ │ └─ HostComponent → 创建子Fiber
│ └─ 向下遍历子节点
│
├─ 归阶段(completeUnitOfWork)
│ ├─ completeWork(current, workInProgress)
│ │ ├─ HostComponent → createInstance()
│ │ ├─ HostText → createTextInstance()
│ │ ├─ appendAllChildren()
│ │ └─ finalizeInitialChildren()
│ └─ 向上回溯父节点
│
└─ Fiber 树构建完成
└─ 等待 commit 阶段插入 DOM
一句话总结:Render = beginWork 向下递 + completeWork 向上归。
2. 初始化阶段
当我们调用:
ts
root.render(<App />, document.getElementById('root'))
流程是这样的:
- 创建
FiberRoot
,内部保存容器信息(containerInfo = #root
)。 - 创建
HostRootFiber
,作为 Fiber 树的根。 - 将
<App />
作为一个 update 放进HostRootFiber.updateQueue
。 - 调度更新 →
scheduleUpdateOnFiber(HostRootFiber)
。
此时,调度器会在合适的时机执行 render 阶段。
3. beginWork ------ 递阶段
render 阶段从 根节点(HostRootFiber) 出发,调用 performUnitOfWork
:
js
function performUnitOfWork(unitOfWork) {
// 1. 进入递阶段
const next = beginWork(unitOfWork.alternate, unitOfWork, renderLanes);
// 2. 保存 props
unitOfWork.memoizedProps = unitOfWork.pendingProps;
// 3. 返回子节点(如果有)
return next;
}
3.1 HostRoot → updateHostRoot
js
function updateHostRoot(current, workInProgress, renderLanes) {
// 处理 updateQueue,拿到 element
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
const nextState = workInProgress.memoizedState;
const nextChildren = nextState.element; // <App />
// 协调子节点,创建 AppFiber
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child; // 返回 AppFiber
}
首次挂载时:
- current 为
null
。 reconcileChildren
内部调用mountChildFibers
,直接创建新 Fiber,不做 diff。
3.2 ClassComponent → constructClassInstance
js
function updateClassComponent(current, workInProgress, Component, nextProps) {
if (workInProgress.stateNode === null) {
// 🔥 首次挂载:创建实例
constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps);
}
// 调用 render() 得到 children
const nextChildren = workInProgress.stateNode.render();
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
特点:
new Component(props)
创建实例。- 执行
render()
,得到 React 元素树。 - 继续递归子节点。
3.3 FunctionComponent → renderWithHooks
js
function updateFunctionComponent(current, workInProgress, Component, nextProps) {
// 首次挂载:设置 Hook 调度器
let nextChildren = renderWithHooks(current, workInProgress, Component, nextProps);
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
特点:
- 使用
HooksDispatcherOnMount
。 - 执行函数组件
Component(nextProps)
。 - 构建 Hook 链表(useState、useEffect)。
3.4 HostComponent → DOM 容器
js
function updateHostComponent(current, workInProgress, renderLanes) {
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
// 协调子节点
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
特点:
- Fiber 本身没有立即创建 DOM。
- 只负责生成子 Fiber(例如
div
下的子节点)。
4. completeUnitOfWork ------ 归阶段
递归到最深的子节点后,进入 归阶段:
js
function completeUnitOfWork(unitOfWork) {
do {
const current = unitOfWork.alternate;
const returnFiber = unitOfWork.return;
// 调用 completeWork
completeWork(current, unitOfWork, subtreeRenderLanes);
// 如果有兄弟,切换到兄弟
if (unitOfWork.sibling !== null) {
workInProgress = unitOfWork.sibling;
return;
}
// 否则回到父节点
unitOfWork = returnFiber;
workInProgress = unitOfWork;
} while (unitOfWork !== null);
}
5. completeWork ------ 创建 DOM
核心逻辑:
js
function completeWork(current, workInProgress, renderLanes) {
switch (workInProgress.tag) {
case HostComponent: {
if (current === null) {
// 🔥 首次挂载:创建 DOM
const instance = createInstance(type, newProps, rootContainer, context);
appendAllChildren(instance, workInProgress);
finalizeInitialChildren(instance, type, newProps, rootContainer, context);
workInProgress.stateNode = instance;
}
return null;
}
case HostText: {
if (current === null) {
workInProgress.stateNode = createTextInstance(newProps);
}
return null;
}
// 组件类节点(ClassComponent、FunctionComponent)不创建 DOM
}
}
执行要点:
- createInstance →
document.createElement(type)
。 - appendAllChildren → 遍历子 Fiber,找到 HostComponent/HostText,
appendChild
。 - finalizeInitialChildren → 设置属性、事件绑定。
- 保存到
fiber.stateNode
。
6. 流程小结
以一个例子:
js
function App() {
return <div className="app"><p>Hello</p></div>;
}
ReactDOM.render(<App />, root);
执行过程:
- HostRoot → 创建 AppFiber。
- AppFiber → 执行
App()
→ 返回<div>
。 - divFiber → 创建 pFiber。
- pFiber → 创建 TextFiber。
- 归阶段 → 从 TextFiber 开始,逐层
createTextNode → createElement → appendChild
。 - 最终,Fiber 树构建完成,DOM 树也就绪。
📌 总结
React Render 首次挂载流程:
- 初始化:创建 FiberRoot 和 HostRootFiber,调度更新。
- 递阶段(beginWork) :向下遍历,基于组件类型创建子 Fiber。
- 归阶段(completeWork) :向上回溯,创建 DOM 实例,收集副作用。
- 完成 Fiber 树:等待 commit 阶段将 DOM 插入页面。
一句话总结:
👉 首次挂载时,render 阶段会完整走一遍 Fiber 树递归,创建所有 Fiber 和 DOM 实例,不存在 Diff 复用。