React 状态更新机制详解 3
React 状态更新机制详解
1. 代码执行阶段(同步执行)
setCount(count + 1); // ①
setCount(count + 1); // ②
setCount(count + 1); // ③
- 此时内存中的
count
值始终为 0(初始值) - 三个
setCount()
调用都是基于当前的count=0
计算:- ① →
setCount(0 + 1)
→ 计划更新为 1 - ② →
setCount(0 + 1)
→ 计划更新为 1 - ③ →
setCount(0 + 1)
→ 计划更新为 1
- ① →
- 关键点:这些调用只是将更新请求加入队列,并不立即修改内存中的
count
值
2. React 内部更新队列处理(异步批处理)
// React 内部伪代码
function processUpdates() {
// 检查更新队列
const updates = [update1, update2, update3];
// 对于直接值更新:
let nextState = currentState; // 初始为0
// 遍历所有更新请求
for (const update of updates) {
if (typeof update === "function") {
// 函数式更新处理
nextState = update(nextState);
} else {
// 直接值更新:总是使用最新的值覆盖
nextState = update; // 这里三次都是 1
}
}
// 最终只保留最后一个值
commitStateChange(nextState); // 最终设置 count=1
}
3. 内存状态变化时间线
阶段 | 内存中的 count 值 | 说明 |
---|---|---|
组件挂载 | 0 | 初始状态 |
用户点击触发事件处理函数 | 0 | 事件处理函数开始执行 |
执行 setCount(count+1) (第一次) | 0 | 计划更新为 1 |
执行 setCount(count+1) (第二次) | 0 | 计划更新为 1(覆盖前一个) |
执行 setCount(count+1) (第三次) | 0 | 计划更新为 1(覆盖前两个) |
事件处理函数结束 | 0 | 所有 setCount 调用完成 |
React 开始处理更新队列 | 0 | 仍在当前渲染周期 |
React 应用最终更新 | → 1 | 更新到 DOM 和内存 |
组件重新渲染 | 1 | 新值生效 |
函数式更新的处理差异
当使用函数式更新时:
setCount((prev) => prev + 1); // ①
setCount((prev) => prev + 1); // ②
setCount((prev) => prev + 1); // ③
React 的处理方式不同:
function processUpdates() {
let nextState = currentState; // 初始为0
// 遍历所有更新函数
nextState = update1(nextState); // 0 => 0+1=1
nextState = update2(nextState); // 1 => 1+1=2
nextState = update3(nextState); // 2 => 2+1=3
commitStateChange(nextState); // 最终设置 count=3
}
核心要点总结
-
同步执行阶段:
- 所有
setCount()
调用都基于当前渲染周期的状态值(本例中始终为 0) - 内存中的状态值在此阶段不会改变
- 只是将更新请求加入队列
- 所有
-
异步更新阶段:
- React 在事件处理完成后统一处理更新队列
- 对于直接值更新 (
setCount(value)
):- 多个更新会被合并
- 只保留最后一个值(本例中三次都是 1)
- 对于函数式更新 (
setCount(updater)
):- 按顺序执行每个更新函数
- 每个函数接收前一个更新结果
-
内存状态变化时机:
- 只在 React 完成队列处理后改变
- 在重新渲染前更新内存值
- 更新后触发组件重新渲染
实验验证
function Counter() {
const [count, setCount] = useState(0);
const handleDirectUpdate = () => {
console.log("【直接更新开始】内存count=", count);
setCount(count + 1);
console.log("更新1计划后 - 内存count=", count);
setCount(count + 1);
console.log("更新2计划后 - 内存count=", count);
setCount(count + 1);
console.log("更新3计划后 - 内存count=", count);
};
console.log("渲染发生,当前count=", count);
return (
<div>
<button onClick={handleDirectUpdate}>测试直接更新 (预期错误)</button>
</div>
);
}
输出结果:
渲染发生,当前count= 0
【直接更新开始】内存count= 0
更新1计划后 - 内存count= 0
更新2计划后 - 内存count= 0
更新3计划后 - 内存count= 0
渲染发生,当前count= 1 (React处理更新后)
这个实验证明了这个理解:在同步执行阶段,内存中的状态值始终保持不变,直到 React 异步处理完所有更新请求。