深入理解 React 渲染管线
从 JSX → createElement → Fiber → DOM 的完整链路、beginWork/completeWork 各 tag 处理逻辑
什么是渲染管线?
定义:渲染管线(Render Pipeline)是 React 将开发者编写的 JSX 代码最终转换为屏幕上像素的完整流水线。理解这条管线,就理解了 React 的核心工作流程。
一、JSX → React Element
JSX 编译
jsx
// 你写的 JSX
<div className="card">
<h1>标题</h1>
<Button onClick={handleClick}>点击</Button>
</div>
// Babel / SWC 编译后(React 17+ 的新 JSX Transform)
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';
_jsxs("div", {
className: "card",
children: [
_jsx("h1", { children: "标题" }),
_jsx(Button, { onClick: handleClick, children: "点击" })
]
});
// 生成的 React Element(普通 JS 对象)
{
$$typeof: Symbol.for('react.element'), // 安全标记,防止 XSS
type: "div", // 字符串=DOM元素,函数=组件
key: null,
ref: null,
props: {
className: "card",
children: [
{ $$typeof: Symbol.for('react.element'), type: "h1", props: { children: "标题" } },
{ $$typeof: Symbol.for('react.element'), type: Button, props: { onClick: fn, children: "点击" } }
]
}
}$$typeof 的安全作用
javascript
// 为什么需要 Symbol.for('react.element')?
// 防止 XSS 攻击:如果服务端返回的 JSON 被注入恶意 React Element
// JSON 无法包含 Symbol → 攻击者无法伪造有效的 React Element
// 服务端返回的恶意数据
const malicious = {
type: "script",
props: { dangerouslySetInnerHTML: { __html: "alert('XSS')" } }
};
// 缺少 $$typeof: Symbol.for('react.element')
// React 不会将其识别为合法元素 → 不渲染二、React Element → Fiber
首次渲染(Mount)
createRoot(container).render(<App />)
↓
创建 FiberRoot 和 HostRoot Fiber
↓
从 App Element 创建 App Fiber
↓
进入 Render 阶段(beginWork + completeWork)Fiber 创建
javascript
function createFiberFromElement(element) {
const { type, key, props } = element;
let tag; // Fiber 的类型标记
if (typeof type === 'function') {
if (type.prototype?.isReactComponent) {
tag = ClassComponent; // 类组件
} else {
tag = FunctionComponent; // 函数组件
}
} else if (typeof type === 'string') {
tag = HostComponent; // DOM 元素(div, span...)
} else if (type === REACT_FRAGMENT_TYPE) {
tag = Fragment;
} else if (type === REACT_SUSPENSE_TYPE) {
tag = SuspenseComponent;
}
// ...其他类型
const fiber = {
tag,
type,
key,
pendingProps: props,
stateNode: null, // DOM 节点或类实例(后面创建)
return: null, // 父 Fiber(后面设置)
child: null,
sibling: null,
alternate: null,
flags: NoFlags,
memoizedState: null, // Hooks 链表头
updateQueue: null,
};
return fiber;
}三、Render 阶段 —— beginWork
beginWork 是递的过程:从根节点向下,为每个 Fiber 创建/更新子 Fiber。
总体流程
javascript
function beginWork(current, workInProgress, renderLanes) {
// current: 当前屏幕上的 Fiber(首次渲染为 null)
// workInProgress: 正在构建的 Fiber
// === Bailout 优化 ===
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (oldProps === newProps && !hasContextChanged() &&
(current.lanes & renderLanes) === 0) {
// Props 没变、Context 没变、没有待处理更新 → 跳过
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
}
// === 根据 tag 分发 ===
switch (workInProgress.tag) {
case FunctionComponent:
return updateFunctionComponent(current, workInProgress, renderLanes);
case ClassComponent:
return updateClassComponent(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(current, workInProgress, renderLanes);
case Fragment:
return updateFragment(current, workInProgress, renderLanes);
case ContextProvider:
return updateContextProvider(current, workInProgress, renderLanes);
case ContextConsumer:
return updateContextConsumer(current, workInProgress, renderLanes);
case MemoComponent:
return updateMemoComponent(current, workInProgress, renderLanes);
case LazyComponent:
return mountLazyComponent(current, workInProgress, renderLanes);
// ...更多类型
}
}FunctionComponent 处理
javascript
function updateFunctionComponent(current, workInProgress, renderLanes) {
const Component = workInProgress.type; // 函数本身
const props = workInProgress.pendingProps;
// 执行函数组件(调用 Hooks)
const children = renderWithHooks(
current,
workInProgress,
Component,
props,
renderLanes
);
// 如果是更新且没有状态变化,尝试 bailout
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress);
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
// Diff 子节点,创建子 Fiber
reconcileChildren(current, workInProgress, children, renderLanes);
return workInProgress.child; // 返回第一个子 Fiber,继续向下
}HostComponent(DOM 元素)处理
javascript
function updateHostComponent(current, workInProgress, renderLanes) {
// type = "div", "span", "input" 等
const type = workInProgress.type;
const newProps = workInProgress.pendingProps;
const oldProps = current?.memoizedProps;
// children 来自 props.children
let nextChildren = newProps.children;
// 优化:纯文本子节点不创建 Fiber
const isDirectTextChild = typeof nextChildren === 'string' ||
typeof nextChildren === 'number';
if (isDirectTextChild) {
nextChildren = null; // 文本节点在 completeWork 中处理
}
// Diff 子节点
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}SuspenseComponent 处理
javascript
function updateSuspenseComponent(current, workInProgress, renderLanes) {
const nextProps = workInProgress.pendingProps;
let showFallback = false;
const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
if (didSuspend) {
// 子组件 throw 了 Promise → 显示 fallback
showFallback = true;
workInProgress.flags &= ~DidCapture;
}
if (showFallback) {
// 渲染 fallback 内容
const fallbackChildren = nextProps.fallback;
return mountSuspenseFallbackChildren(workInProgress, fallbackChildren);
} else {
// 正常渲染子节点
const primaryChildren = nextProps.children;
return mountSuspensePrimaryChildren(workInProgress, primaryChildren);
}
}四、Render 阶段 —— completeWork
completeWork 是归的过程:从叶子节点向上,创建/更新 DOM 节点,收集副作用。
javascript
function completeWork(current, workInProgress, renderLanes) {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode !== null) {
// === 更新 ===
// 比较新旧 props,收集需要更新的属性
const updatePayload = diffProperties(
workInProgress.stateNode,
type,
current.memoizedProps,
newProps
);
// updatePayload = ['className', 'new-class', 'style', { color: 'red' }]
if (updatePayload) {
workInProgress.flags |= Update; // 标记需要更新
workInProgress.updateQueue = updatePayload;
}
} else {
// === 首次渲染 ===
// 创建 DOM 节点
const instance = document.createElement(type);
// 设置属性
setInitialProperties(instance, type, newProps);
// 将所有子 DOM 节点插入
appendAllChildren(instance, workInProgress);
// 绑定 Fiber ↔ DOM
workInProgress.stateNode = instance;
}
// 冒泡收集子树的副作用标记
bubbleProperties(workInProgress);
return null;
}
case HostText: {
const newText = newProps;
if (current !== null && workInProgress.stateNode !== null) {
// 更新文本
if (current.memoizedProps !== newText) {
workInProgress.flags |= Update;
}
} else {
// 创建文本节点
workInProgress.stateNode = document.createTextNode(newText);
}
bubbleProperties(workInProgress);
return null;
}
case FunctionComponent:
case Fragment:
// 函数组件和 Fragment 没有对应 DOM
// 只需要冒泡收集副作用
bubbleProperties(workInProgress);
return null;
}
}副作用冒泡
javascript
function bubbleProperties(completedWork) {
let subtreeFlags = NoFlags;
let child = completedWork.child;
while (child !== null) {
// 收集子节点和子树的所有标记
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;
child = child.sibling;
}
completedWork.subtreeFlags = subtreeFlags;
}
// 作用:Commit 阶段可以通过 subtreeFlags 快速判断子树是否有副作用
// 如果 subtreeFlags === NoFlags → 跳过整棵子树,极大优化 Commit 性能五、Commit 阶段
三个子阶段
javascript
function commitRoot(root) {
const finishedWork = root.finishedWork;
// 1. BeforeMutation 阶段
commitBeforeMutationEffects(finishedWork);
// - getSnapshotBeforeUpdate(类组件)
// - 调度 useEffect(异步)
// 2. Mutation 阶段(操作 DOM)
commitMutationEffects(finishedWork);
// - Placement: insertBefore / appendChild
// - Update: 更新 DOM 属性
// - Deletion: removeChild + 清理
// - useInsertionEffect 回调
// === 切换 Current 树 ===
root.current = finishedWork;
// 3. Layout 阶段
commitLayoutEffects(finishedWork);
// - useLayoutEffect 回调(同步)
// - componentDidMount / componentDidUpdate
// - 更新 ref
// 4. 异步调度 useEffect
scheduleCallback(NormalPriority, () => {
flushPassiveEffects(); // 执行 useEffect 的清理和回调
});
}Mutation 阶段的 DOM 操作
javascript
function commitMutationEffects(fiber) {
// 递归遍历 Fiber 树
if (fiber.subtreeFlags & MutationMask) {
// 子树有副作用,继续遍历子节点
commitMutationEffects(fiber.child);
}
if (fiber.flags & Placement) {
// 新增:找到父 DOM 节点,插入
const parentDOM = getHostParent(fiber);
const before = getHostSibling(fiber); // 找到下一个兄弟 DOM
if (before) {
parentDOM.insertBefore(fiber.stateNode, before);
} else {
parentDOM.appendChild(fiber.stateNode);
}
}
if (fiber.flags & Update) {
// 更新:应用 diffProperties 的结果
const updatePayload = fiber.updateQueue;
updateDOMProperties(fiber.stateNode, updatePayload);
// ['className', 'new-class'] → element.className = 'new-class'
}
if (fiber.flags & Deletion) {
// 删除:递归卸载子树
commitDeletion(fiber);
// removeChild + 执行 useEffect 清理 + 解绑 ref
}
}六、完整渲染流程图
JSX 代码
↓ Babel/SWC 编译
React Element(普通 JS 对象)
↓ createFiberFromElement
Fiber 节点
↓ 构建 WorkInProgress 树
│
├── beginWork(递)
│ ├── FunctionComponent → renderWithHooks → reconcileChildren
│ ├── HostComponent → reconcileChildren
│ └── ...其他类型
│ └── 返回 child Fiber
│
└── completeWork(归)
├── HostComponent → createElement + setProperties + appendChildren
├── 收集副作用标记(flags + subtreeFlags)
└── 返回 sibling 或 return
↓
Commit 阶段(不可中断)
├── BeforeMutation → getSnapshotBeforeUpdate
├── Mutation → DOM 操作(插入/更新/删除)
├── current 切换
├── Layout → useLayoutEffect + ref 更新
└── 异步 → useEffect
↓
屏幕更新面试高频题
Q: JSX 是什么?它是怎么变成 DOM 的?
JSX 是语法糖,由 Babel/SWC 编译为 jsx() 函数调用,返回 React Element(普通 JS 对象,描述 UI 结构)。React 根据 Element 创建 Fiber 节点,通过 Render 阶段(beginWork/completeWork)构建 Fiber 树并创建 DOM 节点,最后在 Commit 阶段将 DOM 操作应用到真实 DOM。
Q: beginWork 和 completeWork 分别做什么?
- beginWork(递):从根节点向下,根据组件类型执行不同逻辑(调用函数组件、Diff 子节点),返回子 Fiber
- completeWork(归):从叶子节点向上,创建/更新 DOM 节点,计算属性 diff,收集副作用标记(subtreeFlags 冒泡)
Q: React 的 $$typeof 是什么?为什么用 Symbol?
$$typeof: Symbol.for('react.element') 是 React Element 的类型标记。使用 Symbol 是为了防止 XSS 攻击:如果攻击者通过 JSON 注入伪造的 React Element,由于 JSON 无法包含 Symbol 值,React 不会将其识别为合法元素,从而拒绝渲染。
Q: Commit 阶段的三个子阶段?
- BeforeMutation:DOM 变更前,执行
getSnapshotBeforeUpdate、调度useEffect - Mutation:执行实际的 DOM 操作(insertBefore/appendChild/removeChild/属性更新),然后切换 current 树
- Layout:DOM 已更新,执行
useLayoutEffect、更新 ref、执行componentDidMount/Update