一、先理清关键前提:React 的状态更新队列
React 内部维护了一个状态更新队列(update queue),当你调用 setXxx 时,并不会立即修改状态,而是把这个“更新操作”加入队列中。React 会在合适的时机(比如事件处理结束后)批量处理队列里的更新,最后一次性更新状态并触发组件重渲染。
二、两种更新方式的本质区别
1. 直接更新:捕获的是“闭包中的旧值”
直接更新(如 setCount(count + 1))的本质是:
- 你传入的是一个值(
count + 1),这个值在调用setCount的瞬间就被计算好了。 - 这里的
count是从组件的闭包中获取的,而闭包中的状态值是“冻结”的(对应上一次渲染的状态),不会因为队列中有其他更新而改变。 - 所以多次调用
setCount(count + 1),计算的都是同一个旧值 +1,最终只生效一次。
2. 函数式更新:React 会把最新状态“喂”给你
函数式更新(如 setCount(prev => prev + 1))的本质是:
- 你传入的不是一个值,而是一个待执行的函数(我们叫它“更新器函数”),这个函数不会立即执行。
- React 处理更新队列时,会按顺序执行这些更新器函数:
- 先拿到当前最新的状态值(初始是上一次渲染的状态);
- 把这个最新值作为参数传给第一个更新器函数,得到新状态;
- 再把这个新状态作为参数传给下一个更新器函数,以此类推;
- 最终所有更新器函数执行完毕,得到最终的状态值。
三、用通俗的例子理解
我们用“排队改数字”来模拟这个过程:
场景:初始 count = 0,连续两次更新
jsx
// 直接更新
setCount(count + 1); // 瞬间计算:0 + 1 = 1,把“1”加入队列
setCount(count + 1); // 瞬间计算:0 + 1 = 1,把“1”加入队列
// 队列处理:最终取最后一个值 1 → count = 1
// 函数式更新
setCount(prev => prev + 1); // 把“(prev) => prev + 1”加入队列
setCount(prev => prev + 1); // 把“(prev) => prev + 1”加入队列
// 队列处理:
// 第一步:prev = 0 → 返回 1
// 第二步:prev = 1 → 返回 2
// 最终 count = 2
四、React 内部的简化逻辑(伪代码)
为了更直观,我们模拟 React 处理更新队列的逻辑:
javascript
// React 内部维护的状态和更新队列
let currentState = 0;
const updateQueue = [];
// 模拟 setCount 方法
function setCount(update) {
if (typeof update === 'function') {
// 函数式更新:把函数加入队列
updateQueue.push(update);
} else {
// 直接更新:把值加入队列
updateQueue.push(() => update);
}
}
// 模拟 React 批量处理更新
function processQueue() {
// 遍历队列,依次执行更新器函数,确保每次用最新状态
updateQueue.forEach(updateFn => {
currentState = updateFn(currentState);
});
updateQueue.length = 0; // 清空队列
}
// 测试函数式更新
setCount(prev => prev + 1);
setCount(prev => prev + 1);
processQueue();
console.log(currentState); // 输出 2(正确)
// 测试直接更新
currentState = 0;
setCount(currentState + 1);
setCount(currentState + 1);
processQueue();
console.log(currentState); // 输出 1(错误)
总结
- 核心原因:函数式更新传入的是“更新器函数”,而非立即计算的值;React 处理队列时,会按顺序将最新的状态值传入每个更新器函数,确保每个函数都基于前一次更新的结果计算。
- 闭包无关:函数式更新绕开了组件闭包中“冻结的旧状态”,直接从 React 内部的状态管理模块获取最新值。
- 关键结论:只要新状态依赖旧状态,函数式更新就能保证拿到最新值,这是由 React 对更新队列的处理逻辑决定的。
简单来说,直接更新是“先算好值再排队”,函数式更新是“先排队,等轮到自己时再用最新值计算”,这就是前者拿不到最新值、后者可以的根本原因。