在 React 的 Stack Reconciler 时代(React 15 及之前),更新是同步的,组件树递归渲染过程中直接进行 DOM 更新操作。这种方式虽然简单,但缺乏中断和恢复能力,无法应对复杂应用中对性能和响应性的需求。
随着 React 16 引入 Fiber 架构,副作用收集成为协调器的重要组成部分。Fiber 将整个更新过程拆分为多个阶段:render phase
和 commit phase
,其中:
render phase
负责构建新的 Fiber 树并标记需要执行的副作用。commit phase
负责将这些副作用批量执行到真实 DOM 上。这种设计使得 React 可以更灵活地调度任务,并支持并发模式(Concurrent Mode)下的中断与优先级调度。
ReactFiberCommitWork.old.js
:定义了副作用执行的具体逻辑。ReactFiberWorkLoop.old.js
:工作循环负责触发副作用收集。ReactFiberEffects.old.js
:处理副作用队列的收集和清理。副作用(Effect)是指那些在渲染后需要执行的操作,主要包括:
Placement
(插入)Update
(更新)Deletion
(删除)React 在 completeUnitOfWork()
阶段通过 completeWork()
方法标记这些副作用,并将其添加到父节点的 effectList
中,最终在 commitRoot()
阶段统一执行。
beginWork()
↓
completeWork() ——> 收集当前节点副作用
↓
appendAllChildren() / reconcileChildren()
↓
递归完成所有子节点的 completeWork()
↓
将当前节点 effect 添加到父节点 effectList 中
↓
commitRoot()
↓
遍历 effectList 执行副作用
这段代码是 React Fiber 架构中 completeWork
函数的简化实现,负责完成 Fiber 节点的处理工作。下面是对代码的详细注释:
/**
* 完成 Fiber 节点的处理工作
* @param {Fiber | null} current 当前 Fiber 节点(可能为 null)
* @param {Fiber} workInProgress 工作进中的 Fiber 节点
* @returns {Fiber | null} 返回下一个要处理的兄弟节点或 null
*/
function completeWork(current: Fiber | null, workInProgress: Fiber): Fiber | null {
const newProps = workInProgress.pendingProps;
// 根据 Fiber 节点的类型执行不同的处理逻辑
switch (workInProgress.tag) {
case HostComponent: { // 处理 DOM 组件节点
const type = workInProgress.type; // DOM 标签名(如 'div', 'span' 等)
if (current !== null && current.stateNode != null) {
// 如果是更新阶段,更新 DOM 属性
updateHostComponent(
current,
workInProgress,
type,
newProps
);
} else {
// 如果是初次创建,创建 DOM 实例
const instance = createInstance(
type,
newProps,
rootContainerInstance, // 根容器实例
hostContext, // 宿主环境上下文
internalInstanceHandle // 内部实例句柄
);
// 附加所有子节点到父实例
appendAllChildren(
instance,
workInProgress,
false, // 是否为 DOM 属性
false // 是否为隐藏的(如 CSS hidden)
);
// 设置 Fiber 节点的 stateNode 为创建的 DOM 实例
workInProgress.stateNode = instance;
// 初始化子节点的属性(如自动聚焦等)
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
hostContext
);
}
break;
}
case HostText: { // 处理文本节点
const newText = newProps; // 文本内容就是 props
if (current && current.stateNode !== null) {
// 如果是更新阶段,检查文本内容是否变化
const oldText = current.memoizedProps;
if (oldText !== newText) {
markWorkInProgressReceivedUpdate(); // 标记有更新需要处理
}
} else {
// 如果是初次创建,创建文本实例
const instance = createTextInstance(
newText,
rootContainerInstance,
hostContext,
internalInstanceHandle
);
// 设置 Fiber 节点的 stateNode 为创建的文本实例
workInProgress.stateNode = instance;
}
break;
}
default:
// 其他类型的节点不做特殊处理
break;
}
// 处理完当前节点后,决定下一个要处理的节点
if (workInProgress.child !== null) {
// 如果有子节点,继续向下处理子节点
return workInProgress.child;
} else {
// 如果没有子节点,向上查找兄弟节点
let node: Fiber | null = workInProgress;
while (node !== null) {
if (node.sibling !== null) {
// 找到兄弟节点,返回它
return node.sibling;
}
if (node.return === null) {
// 如果没有父节点,返回 null(到达根节点)
return null;
}
// 继续向上查找
node = node.return;
}
return null;
}
}
关键点说明:
节点类型处理:
HostComponent
:处理 DOM 元素节点HostText
:处理文本节点DOM 组件处理流程:
updateHostComponent
更新 DOM 属性createInstance
创建 DOM 实例appendAllChildren
附加所有子节点finalizeInitialChildren
初始化子节点属性文本节点处理流程:
createTextInstance
创建文本节点遍历逻辑:
返回值:
这个函数是 Fiber 架构中协调(reconciliation)过程的重要组成部分,负责将 Fiber 树转换为实际的 DOM 结构。
这段代码是 React Fiber 架构中 commitRoot
函数的简化实现,负责将工作完成(finished)的 Fiber 树提交到 DOM。下面是对代码的详细注释:
/**
* 提交根节点及其副作用到 DOM
* @param {FiberRoot} root Fiber 根节点
*/
function commitRoot(root) {
// 获取已完成的工作(即 workInProgress 树的根节点)
const finishedWork = root.finishedWork;
// 获取已完成的工作对应的更新优先级(lane)
const lanes = root.finishedLanes;
// 重置 finishedWork 和 finishedLanes,准备下一次更新
root.finishedWork = null;
root.finishedLanes = NoLanes;
// 提交所有副作用(如 DOM 插入、更新、删除等)
commitMutationEffects(finishedWork, root);
// 将当前指针(current)切换到已完成的工作(即 workInProgress 树)
root.current = finishedWork;
}
关键点说明:
参数:
root
:Fiber 根节点对象,包含整个 Fiber 树的信息获取已完成的工作:
finishedWork
:表示已经完成协调(reconciliation)过程的 Fiber 树根节点lanes
:表示已完成工作的更新优先级(在 React 的并发模式下使用)重置状态:
finishedWork
和 finishedLanes
重置为初始状态,为下一次更新做准备提交副作用:
commitMutationEffects
:执行所有挂起的副作用(如 DOM 操作)
切换当前指针:
root.current
指向 finishedWork
,表示现在 finishedWork
树成为新的当前树副作用类型:
commitMutationEffects
中会处理多种副作用,如:
Placement
:插入新节点Update
:更新现有节点Deletion
:删除节点这个函数是 React 渲染过程的最后阶段,负责将协调阶段产生的变更实际应用到 DOM 上。在 React 的并发模式下,这个阶段是不可中断的,以确保 UI 的一致性。
这段代码是 React Fiber 架构中 commitMutationEffects
函数的简化实现,负责处理所有挂起的副作用(如 DOM 插入、更新和删除)。下面是对代码的详细注释:
/**
* 提交所有挂起的副作用(如 DOM 插入、更新和删除)
* @param {Fiber} finishedWork 已完成协调的 Fiber 节点
* @param {FiberRoot} root Fiber 根节点
*/
function commitMutationEffects(finishedWork: Fiber, root: FiberRoot) {
// 从 finishedWork 的第一个副作用节点开始遍历
let nextEffect: Fiber | null = finishedWork.firstEffect;
// 遍历所有带有副作用的 Fiber 节点
while (nextEffect !== null) {
// 获取当前节点的副作用标记
const effectTag = nextEffect.effectTag;
// 处理 Placement 副作用(插入新节点)
if ((effectTag & Placement) !== NoEffect) {
commitPlacement(nextEffect); // 执行实际的 DOM 插入操作
nextEffect.effectTag &= ~Placement; // 清除 Placement 标记
}
// 处理 Update 副作用(更新现有节点)
if ((effectTag & Update) !== NoEffect) {
commitWork(nextEffect, nextEffect.alternate); // 执行实际的 DOM 更新操作
nextEffect.effectTag &= ~Update; // 清除 Update 标记
}
// 处理 Deletion 副作用(删除节点)
if ((effectTag & Deletion) !== NoEffect) {
commitDeletion(nextEffect); // 执行实际的 DOM 删除操作
nextEffect.effectTag &= ~Deletion; // 清除 Deletion 标记
}
// 移动到下一个带有副作用的节点
nextEffect = nextEffect.nextEffect;
}
}
关键点说明:
副作用遍历:
finishedWork.firstEffect
开始遍历所有带有副作用的 Fiber 节点nextEffect
指针在副作用链表中移动副作用类型处理:
commitPlacement
执行实际的插入Placement
标记commitWork
执行实际的更新Update
标记commitDeletion
执行实际的删除Deletion
标记副作用标记清除:
副作用链表:
nextEffect
指针形成一个链表不可中断性:
这个函数是 React 渲染过程的最后阶段之一,负责将协调阶段产生的变更实际应用到 DOM 上。在 React 的并发模式下,这个阶段是不可中断的,以确保 UI 的一致性。
以下是一个简化版的副作用收集模拟实现:
// 模拟 Fiber 对象结构
class Fiber {
constructor(tag, key, elementType, pendingProps, mode) {
this.tag = tag; // 类型:FunctionComponent, ClassComponent 等
this.key = key; // 用于列表比对
this.elementType = elementType;// 元素类型(如函数组件、类组件等)
this.pendingProps = pendingProps;// 待处理的 props
this.mode = mode; // 渲染模式(如 ConcurrentMode)
this.alternate = null; // 指向另一个树的节点
this.child = null; // 第一个子节点
this.sibling = null; // 下一个兄弟节点
this.return = null; // 父节点
this.stateNode = null; // DOM 或组件实例
this.memoizedState = null; // 状态快照
this.updateQueue = null; // 更新队列
this.effectTag = 'NoEffect';// 副作用标记(如 Placement, Update, Deletion)
this.nextEffect = null; // 副作用链表中的下一个节点
this.firstEffect = null; // 副作用链表中的第一个节点
this.lastEffect = null; // 副作用链表中的最后一个节点
}
addEffect(effectTag) {
const effect = { effectTag, fiber: this };
if (!this.parent) return;
if (!this.parent.firstEffect) {
this.parent.firstEffect = effect;
this.parent.lastEffect = effect;
} else {
this.parent.lastEffect.nextEffect = effect;
this.parent.lastEffect = effect;
}
}
}
// 模拟 beginWork
function beginWork(current, workInProgress) {
console.log('Begin work on:', workInProgress.key || 'root');
return workInProgress.child;
}
// 模拟 completeWork 并收集副作用
function completeWork(workInProgress) {
console.log('Complete work on:', workInProgress.key || 'root');
// 模拟不同类型的节点处理
switch (workInProgress.tag) {
case 'HostComponent':
if (!workInProgress.stateNode) {
// 新增节点
workInProgress.effectTag = 'Placement';
workInProgress.addEffect(workInProgress.effectTag);
} else {
// 更新节点
workInProgress.effectTag = 'Update';
workInProgress.addEffect(workInProgress.effectTag);
}
break;
case 'HostText':
if (!workInProgress.stateNode) {
workInProgress.effectTag = 'Placement';
workInProgress.addEffect(workInProgress.effectTag);
}
break;
default:
break;
}
}
// 模拟 commit 阶段
function commitMutationEffects(finishedWork) {
console.log('Committing effects...');
let effect = finishedWork.firstEffect;
while (effect) {
switch (effect.effectTag) {
case 'Placement':
console.log(`Place node: ${effect.fiber.key}`);
break;
case 'Update':
console.log(`Update node: ${effect.fiber.key}`);
break;
case 'Deletion':
console.log(`Delete node: ${effect.fiber.key}`);
break;
default:
break;
}
effect = effect.nextEffect;
}
}
// 主工作循环
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
const next = beginWork(current, unitOfWork);
if (next === null) {
completeWork(unitOfWork);
}
return next;
}
function workLoop(isYieldy) {
let unitOfWork = workInProgressFiber;
while (unitOfWork !== null) {
unitOfWork = performUnitOfWork(unitOfWork);
}
if (!isYieldy && unitOfWork === null) {
commitMutationEffects(workInProgressFiber);
}
}
let currentFiber = new Fiber('HostRoot', null, null, null, 'ConcurrentMode');
let workInProgressFiber = null;
function createWorkInProgressFromCurrent(current) {
if (!current.alternate) {
workInProgressFiber = new Fiber(
current.tag,
current.key,
current.elementType,
current.pendingProps,
current.mode
);
workInProgressFiber.alternate = current;
current.alternate = workInProgressFiber;
} else {
workInProgressFiber = current.alternate;
workInProgressFiber.pendingProps = current.pendingProps;
workInProgressFiber.effectTag = 'NoEffect';
}
return workInProgressFiber;
}
function scheduleUpdate() {
workInProgressFiber = createWorkInProgressFromCurrent(currentFiber);
workLoop(false);
}
scheduleUpdate();
设计模式 | 应用场景 |
---|---|
观察者模式 | effectTag 表示要执行的副作用,由 completeWork 观察并收集 |
链表模式 | firstEffect 和 lastEffect 形成副作用链表,便于 commit 阶段遍历 |
策略模式 | 不同类型 Fiber(如 HostComponent、FunctionComponent)使用不同副作用处理策略 |
模板方法模式 | performUnitOfWork() 提供通用流程,beginWork 和 completeWork 是具体实现 |
什么是 React 的副作用?常见的副作用有哪些?
Placement
, Update
, Deletion
。副作用是如何被收集的?在哪里被记录?
completeWork()
阶段通过 effectTag
标记,并加入到父节点的 firstEffect
链表中。为什么要在 render 阶段收集副作用,而不是直接操作 DOM?
effectTag 的含义是什么?它是如何表示多个副作用的?
Placement | Update
。副作用是如何执行的?执行顺序是怎样的?
commitRoot()
中遍历 effectList
,按照深度优先顺序执行副作用。effectList 是如何构建的?父子节点之间如何传递副作用?
effectList
中,形成一个单向链表。React 如何处理嵌套组件的副作用?
completeWork()
处理每个节点,确保所有子节点的副作用都被正确收集。为什么不能在 render 阶段直接修改 DOM?这样做会带来什么问题?
React 如何优化副作用的执行效率?
在 Concurrent Mode 下,副作用是否有可能被多次执行?为什么?
Fiber 副作用收集机制是 React 实现高效、可中断、可恢复更新的关键部分。它通过 effectTag
和 effectList
的设计,在 render phase
安全地收集副作用,并在 commit phase
统一执行,保证了 DOM 更新的一致性和性能。理解这一机制有助于深入掌握 React 的内部运行原理,为性能优化和调试提供理论支撑。