【React源码19】深入学习React 源码实现—— setState 中的双端环状链表处理
🧠 深入学习React源码实现之 setState 中的双端环状链表处理
一、引言:为什么 React 的 setState 需要使用双端环状链表?
在 React 的早期版本中,setState 是一个同步更新操作,这导致频繁调用时容易造成性能瓶颈。随着 React Fiber 架构的引入(React 16+),setState 的更新机制发生了重大变化:
React 开始使用“链式更新队列”来管理多个
setState调用。
而为了提升性能和并发能力,React 在内部使用了双端环状链表(Double-ended Circular Linked List)结构来组织这些更新任务。
二、历史演进与背景介绍
✅ React 15 及以前
- 使用栈协调器(Stack Reconciler)
setState是同步的,每次调用都会触发一次 re-render- 不可中断,无法支持并发更新
✅ React 16 引入 Fiber 架构
- 将组件树转化为 Fiber 树结构
- 支持异步更新、优先级调度
- 更新操作不再立即执行,而是被放入链表队列
✅ React 17~18 并发模式(Concurrent Mode)
- 多个
setState被合并为一个更新任务 - 使用双端环状链表来高效插入、合并和调度更新任务
三、源码路径与关键文件
React 的 setState 实现涉及到多个核心模块,主要集中在以下几个包中:
| 包名 | 文件路径 | 功能 |
|---|---|---|
react-reconciler | ReactFiberClassComponent.js | setState 的入口逻辑 |
react-reconciler | ReactUpdateQueue.old.js / ReactUpdateQueue.new.js | 管理状态更新队列 |
react-reconciler | ReactFiberWorkLoop.js | 协调器主循环,处理更新 |
scheduler | Scheduler.js | 任务调度器,控制更新优先级 |
我们重点关注以下两个文件中的实现:
ReactUpdateQueue.old.js:负责维护更新队列;ReactFiberClassComponent.js:setState方法的绑定与调用流程。
四、算法设计思路:双端环状链表的原理与优势
✅ 什么是双端环状链表?
- 每个节点都有
next和prev指针; - 头尾相连,形成闭环;
- 插入/删除操作复杂度为 O(1);
- 支持从头或尾部快速插入新节点。
✅ 为什么 React 使用它?
- 多个
setState可能来自不同优先级的任务; - 链表结构便于插入、合并、排序;
- 支持高并发下的更新调度;
- 可以根据优先级中断当前更新,先处理高优先级任务。
五、详细步骤解析:setState 如何构建和处理双端环状链表
步骤 1:调用 setState() 方法
this.setState({ count: this.state.count + 1 });
这个方法最终会调用到 React 内部的 enqueueSetState 函数。
步骤 2:创建更新对象(update)
const update = {
payload, // 要更新的状态对象
callback, // setState 的回调函数
next: null // 指向下一个 update 节点
};
步骤 3:将 update 添加到 fiber.updateQueue 中
function enqueueUpdate(fiber, update) {
// 获取 fiber 节点的更新队列
const queue = fiber.updateQueue;
// 获取更新队列的最后一个 update 节点
const last = queue.last;
// 检查更新队列是否为空
if (last === null) {
// 如果更新队列为空,这是第一次添加 update
// 将队列的第一个和最后一个节点都设置为新的 update
queue.first = update;
queue.last = update;
} else {
// 如果更新队列不为空,将新的 update 插入到链表尾部
// 获取队列的第一个 update 节点
const first = queue.first;
// 将当前最后一个 update 的 next 指向新的 update
last.next = update;
// 更新队列的最后一个节点为新的 update
queue.last = update;
// 将新的 update 的 next 指向队列的第一个 update,形成环形链表
update.next = first;
}
}
代码逻辑总结
-
获取更新队列:
const queue = fiber.updateQueue;:从fiber节点中获取更新队列queue。
-
检查队列是否为空:
const last = queue.last;:获取队列的最后一个update节点。if (last === null):如果last为null,说明队列为空。
-
初始化队列:
queue.first = update;和queue.last = update;:如果队列为空,将队列的第一个和最后一个节点都设置为新的update。
-
插入到链表尾部:
const first = queue.first;:获取队列的第一个update节点。last.next = update;:将当前最后一个update的next指向新的update。queue.last = update;:更新队列的最后一个节点为新的update。update.next = first;:将新的update的next指向队列的第一个update,形成环形链表。
环形链表的作用
- 高效遍历:环形链表的结构使得 React 在处理更新时可以高效地遍历整个更新队列,而不需要额外的条件判断来处理链表末尾。
- 循环处理:React 在处理更新时,通常会循环遍历更新队列,直到所有更新都被处理完毕。环形链表的结构简化了这种循环逻辑。
通过这种方式,enqueueUpdate 函数确保了更新队列的高效管理和遍历,是 React 状态管理和 Fiber 架构的重要组成部分。
步骤 4:标记 fiber 需要重新渲染
scheduleWork(fiber);
步骤 5:在 WorkLoop 中消费更新队列
processUpdateQueue 函数是 React 内部用于处理状态更新的核心逻辑之一。它的主要作用是遍历更新队列(updateQueue),合并所有的状态更新(update),并将最终的状态应用到组件中。以下是对该函数的详细解析和每行注释:
完整代码与注释
function processUpdateQueue(current, workInProgress) {
// 获取当前组件的 memoizedState,作为初始状态
let newBaseState = current.memoizedState;
// 初始化新的更新队列的头部和尾部
let newFirst = null;
let newLast = null;
// 获取更新队列的第一个 update
let update = queue.first;
// 遍历更新队列,直到回到起点(环形链表)
do {
// 获取当前 update 的 payload(即 action 或 reducer 函数)
const action = update.payload;
// 使用 reducer 合并当前状态和 action,生成新的状态
newBaseState = reducer(newBaseState, action);
// 移动到下一个 update
update = update.next;
} while (update !== null && update !== queue.first); // 循环条件:update 不为 null 且未回到起点
// 更新队列的头部和尾部(这里似乎有误,实际应为 newFirst 和 newLast)
queue.first = newFirst;
queue.last = newLast;
// 将最终合并后的状态应用到 workInProgress 节点的 memoizedState
workInProgress.memoizedState = newBaseState;
}
代码逻辑总结
-
初始化状态:
let newBaseState = current.memoizedState;:从当前组件(current)的memoizedState中获取初始状态。
-
初始化更新队列:
let newFirst = null;和let newLast = null;:初始化新的更新队列的头部和尾部,用于存储处理后的更新(实际代码中似乎未使用,可能是遗漏)。
-
遍历更新队列:
let update = queue.first;:从更新队列的第一个update开始遍历。do {...} while (update !== null && update !== queue.first);:循环遍历更新队列,直到回到起点(环形链表)。
-
合并状态:
const action = update.payload;:获取当前update的payload(通常是状态更新的action或reducer函数)。newBaseState = reducer(newBaseState, action);:使用reducer合并当前状态和action,生成新的状态。
-
更新队列指针:
update = update.next;:移动到下一个update。
-
更新队列的头部和尾部:
queue.first = newFirst;和queue.last = newLast;:更新队列的头部和尾部(实际代码中newFirst和newLast未被赋值,可能是遗漏或简化)。
-
应用最终状态:
workInProgress.memoizedState = newBaseState;:将最终合并后的状态应用到workInProgress节点的memoizedState。
注意事项
newFirst和newLast未被赋值:在提供的代码中,newFirst和newLast始终为null,这可能是代码的简化或遗漏。在实际实现中,可能需要将这些变量用于存储处理后的更新队列。reducer函数:reducer是一个用于合并状态的函数,通常是组件的useReducer或类组件的reducer函数。- 环形链表遍历:
do...while循环确保遍历整个环形链表,直到回到起点。
总结
processUpdateQueue 函数是 React 状态管理的核心部分,负责遍历和合并状态更新,并将最终状态应用到组件中。理解这一机制对于深入掌握 React 的状态管理和 Fiber 架构非常重要。
六、完整代码实现与注释(简化版模拟)
下面是一个简化的 JavaScript 版本,用于演示 React 中如何通过双端环状链表处理 setState:
模拟了 React 的 Fiber 架构中状态更新的核心逻辑,包括 Fiber 类、Update 类、enqueueUpdate 方法和 processUpdateQueue 函数。以下是对代码的详细解析和每行注释:
完整代码与注释
// 模拟 Fiber 对象
class Fiber {
constructor() {
// 初始化更新队列,包含 first 和 last 指针
this.updateQueue = {
first: null,
last: null
};
// 初始化 memoizedState,用于存储组件的当前状态
this.memoizedState = {};
}
// 将更新(update)添加到更新队列中
enqueueUpdate(update) {
const queue = this.updateQueue;
// 如果队列为空,初始化队列
if (queue.last === null) {
queue.first = update;
queue.last = update;
update.next = update; // 自指形成环形链表
} else {
// 将新 update 插入到队列尾部
update.next = queue.last.next;
queue.last.next = update;
queue.last = update;
}
}
// 获取当前 memoizedState
getState() {
return this.memoizedState;
}
}
// 模拟 Update 对象
class Update {
constructor(payload, callback) {
// payload 是状态更新的数据
this.payload = payload;
// callback 是可选的回调函数
this.callback = callback || null;
// next 指针用于形成链表
this.next = null;
}
}
// 模拟 setState 的更新逻辑
function setState(fiber, payload) {
// 创建一个新的 Update 对象
const update = new Update(payload);
// 将 update 添加到 fiber 的更新队列中
fiber.enqueueUpdate(update);
}
// 合并更新队列
function processUpdateQueue(fiber) {
const queue = fiber.updateQueue;
// 获取当前状态
let currentState = fiber.getState();
// 初始化新的更新队列的头部和尾部
let newFirst = null;
let newLast = null;
// 获取队列的第一个 update
let update = queue.first;
// 如果队列不为空,遍历整个环形链表
if (update !== null) {
do {
// 合并当前状态和 update 的 payload
const nextState = { ...currentState, ...update.payload };
currentState = nextState;
// 移动到下一个 update
update = update.next;
// 更新新的更新队列的指针
if (newLast === null) {
newFirst = update;
} else {
newLast.next = update;
}
newLast = update;
} while (update !== queue.first); // 遍历整个环形链表
}
// 更新 fiber 的更新队列
fiber.updateQueue.first = newFirst;
fiber.updateQueue.last = newLast;
// 更新 fiber 的 memoizedState
fiber.memoizedState = currentState;
}
代码逻辑总结
-
Fiber类:updateQueue:存储组件的更新队列,包含first和last指针。memoizedState:存储组件的当前状态。enqueueUpdate:将update添加到更新队列中,形成环形链表。getState:获取当前状态。
-
Update类:payload:状态更新的数据。callback:可选的回调函数。next:指向下一个update,用于形成链表。
-
setState函数:- 创建一个新的
Update对象,并将其添加到fiber的更新队列中。
- 创建一个新的
-
processUpdateQueue函数:- 遍历更新队列,合并所有
update的payload到当前状态。 - 更新
fiber的memoizedState和更新队列的指针。
- 遍历更新队列,合并所有
注意事项
- 环形链表遍历:
do...while循环确保遍历整个环形链表,直到回到起点。 - 状态合并:
nextState = { ...currentState, ...update.payload };使用对象展开语法合并状态。 - 更新队列指针:
newFirst和newLast用于更新队列的重新组织(实际代码中可能未完全实现)。
总结
这段代码模拟了 React 的 Fiber 架构中状态更新的核心逻辑,包括如何将更新添加到队列、遍历队列并合并状态。理解这一机制对于深入掌握 React 的状态管理和 Fiber 架构非常重要。
七、设计模式分析
✅ 1. 观察者模式(Observer Pattern)
setState相当于通知 React 当前组件状态已变更;- React 内部监听状态变化并决定是否需要更新视图。
✅ 2. 状态模式(State Pattern)
- 每次
setState本质上是在修改组件的“状态”; - React 根据新的状态决定是否进行 re-render。
✅ 3. 命令模式(Command Pattern)
- 每个
setState调用都封装为一个“命令”对象; - 这些命令按优先级排队执行。
✅ 4. 队列模式(Queue Pattern)
- 所有
setState操作被统一放入更新队列; - 支持批量更新优化。
✅ 5. 链表结构(Linked List)
- 使用双端环状链表结构管理多个
setState; - 插入和合并效率更高。
八、高频面试题解析(含答案)
Q1: setState 是同步还是异步的?为什么?
- 在合成事件和生命周期钩子中是异步的(React 批量合并);
- 在 setTimeout、Promise.then 中是同步的;
- React 使用链表结构缓存更新,等合适时机再批量处理。
Q2: setState 是如何合并多个更新的?
- 多个
setState被封装为多个Update节点; - 插入到环状链表中;
- 在 commit 阶段统一合并状态。
Q3: 为什么 React 使用双端环状链表而不是数组?
- 插入/删除效率更高(O(1));
- 支持多优先级更新;
- 更适合并发模式下的任务调度。
Q4: setState 的更新顺序是 FIFO 还是 LIFO?
- 默认是 FIFO,但可通过
priority控制; - 高优先级任务可以插队执行。
Q5: setState 是否一定触发 re-render?
- 如果新旧状态相同,则不会触发;
- 可配合
shouldComponentUpdate或React.PureComponent优化。
Q6: 如何在 setState 完成后执行回调?
setState({ count: 1 }, () => console.log('更新完成'))- 回调会被封装进
Update对象,在 commit 阶段执行。
Q7: React 是如何避免重复更新的?
- 使用链表结构,允许合并多个连续的
setState; - 使用优先级系统控制执行顺序;
- 利用上下文隔离策略避免无效更新。
Q8: 双端环状链表的优点是什么?
- 支持两端快速插入;
- 支持优先级调度;
- 支持批量更新;
- 非常适合并发更新场景。
Q9: setState 和 useState 的更新机制有什么区别?
setState是类组件使用的 API;useState是函数组件使用的 Hook;- 底层机制一致,都是通过链表结构管理更新;
- 差别在于上下文环境和闭包捕获方式。
Q10: React 如何保证 setState 的更新顺序?
- 使用链表结构记录更新顺序;
- 在 commit 阶段按顺序执行;
- 支持优先级排序(如用户交互 > 数据加载)。
九、总结:setState 中双端环状链表的核心要点
| 特性 | 说明 |
|---|---|
| 数据结构 | 双端环状链表 |
| 存储位置 | 每个 Fiber 节点上的 updateQueue |
| 插入方式 | 按照优先级插入链表 |
| 合并策略 | 支持批处理和优先级调度 |
| 性能优化 | 避免多次 render,提高响应速度 |
| 设计模式 | 观察者、命令、队列、链表结构 |
| 面试重点 | 合并机制、异步更新、更新队列、链表结构 |
十、结语
掌握 setState 内部如何使用双端环状链表进行状态更新,不仅可以帮助你深入理解 React 的更新机制,也能在实际开发中写出更高效的组件逻辑。
如果你正在准备前端高级工程师岗位的面试,这份内容将极大增强你的技术深度与竞争力。🚀
💡 老曹提示:本文适用于熟悉 React 基础、有一定源码阅读能力的同学。建议结合 React GitHub 仓库 一起阅读。
更多推荐


所有评论(0)