所有刚刚学完React的新手都会遇到这种情况:写了个按钮点击计数的功能,代码看起来没问题,但点完按钮后,页面上的数字就是不更新?这时候可能会怀疑是不是哪里写错了?
其实问题出在状态更新方式上!
今天我们就来聊聊,为什么 React 要求我们不能直接修改状态对象,而是必须创建一个新对象来更新状态。
一、一个常见的误区
假设我们想记录用户左右按钮的点击次数,可能会写出这样的代码:
const [clicks, setClicks] = useState({ left: 0, right: 0 });
const handleLeftClick = () => {
clicks.left++; // ❌ 直接修改对象属性
setClicks(clicks); // ❌ 传递同一个对象
};
这段代码乍看之下能运行,但如果你点完按钮发现页面没变,那就说明出了问题。
二、React 为什么这么设计?
React 的状态管理有个核心原则:不可变性。简单来说,就是要求我们不能直接修改现有的状态对象,而是要通过创建一个新的对象来更新状态。
1. React 是怎么判断状态有没有变?
React 其实是通过比较对象的“身份证号”(引用地址)来判断是不是同一个东西。
- 如果你直接修改原对象(比如
clicks.left++),那这个对象的“身份证号”其实没变。 - 调用
setClicks(clicks)时,React 会对比新旧对象的“身份证号”,发现是一样的,就会跳过更新。
结果:状态值虽然改了,但页面没重新渲染,导致数据和 UI 不一致。
三、直接修改状态的“副作用”
1. 状态更新被“偷偷忽略”
比如你写了一个点击按钮计数的功能,但因为直接修改了状态对象,React 根本没意识到状态变了,所以页面上的数字不会更新。
2. 性能优化失效
React 的 React.memo 或 useMemo 等优化手段,依赖的是“状态没变就不用重新计算”。如果你直接修改对象,这些优化就失效了,反而可能导致性能问题。
3. 数据同步混乱
如果多个组件共享同一个状态对象,直接修改会导致部分组件看不到最新的数据,进而引发奇怪的 bug。
四、正确的做法:创建新对象
React 要求我们通过创建新对象来更新状态。比如:
const handleLeftClick = () => {
setClicks({
...clicks, // 复制旧对象的所有属性
left: clicks.left + 1 // 修改指定属性
});
};
为什么这么做?
...clicks会生成一个新对象,它的“身份证号”和原来的clicks不一样。- React 检测到新旧对象不同,就会触发重新渲染。
五、拆分状态 vs 合并状态
上面的例子中,我们把 left 和 right 放在一个对象里,但其实这可能有点“多此一举”。
1. 更简单的写法
如果状态之间没什么关联,完全可以拆开写:
const [left, setLeft] = useState(0);
const [right, setRight] = useState(0);
const handleLeftClick = () => {
setLeft(left + 1); // ✅ 直接更新单一状态
};
- 代码更直观,不需要处理对象合并。
- 修改一个状态不会影响另一个状态。
2. 什么时候需要合并状态?
只有在以下情况才需要合并状态:
- 多个状态属性高度相关(比如表单字段)。
- 需要一次性更新多个属性(比如同时修改
x和y坐标)。
如果你喜欢类似的前端新生常见技术分享,欢迎关注我,我是汤圆 —— 一个前端菜鸟!😄