在用 React 的 useReducer(或 Redux)管理数组状态时,很多同学习惯直接在 reducer 里用 pop()、push()、splice() 等原地操作方法更新数据,觉得“反正最后 return 了一个新数组就没事”。但其实,这种写法会导致莫名的“多删除”“多添加”甚至状态错乱,引发隐晦难查的 bug!为什么?
1. 认识 React 状态的不可变原则
React 的状态管理流强调不可变性(Immutability):
- 原始 state 只能读,绝不能直接修改!
- 任何修改都必须发生在 state 的拷贝(新对象)上,并返回这个新对象。
- 原因在于 React 判断状态变化用的是“引用浅比较”,如果直接改了原对象的内容,哪怕你 return 了
[...state],原 state 早变样了,可能导致 React 的状态同步和更新调度出现意外。
2. 为什么直接 pop(state) 会“多删一次”?
- 直接
state.pop(); return [...state]实际上先把 state (原数组) 改变了。 - 在 React 18+、开发环境或者严格模式下,Reducer 可能会被多次调用以做一致性检测。这时,你修改的 state 会被多次 pop,导致队列一次性丢失多个元素!
- 在时间旅行、撤销重做、DevTools 等场景下,原 state 修改会让状态快照失真、维护困难。
3. 正确的实践方式
-
永远不要直接修改 state!
-
使用拷贝后修改的写法,如:
let newState = [...state]; newState.pop(); return newState; -
其他像
push、unshift、splice等也要同理。
4. 官方出处
- React 官方文档:
你应该把 Reducer 里的 state 当作只读对象。千万不要直接修改 state,仅返回新对象。
5. 代码对比实例
错误示例:
if(action.type==="remove"){
state.pop(); // 直接改变原始队列
return [...state];
}
正确示例:
if(action.type==="remove"){
let newState = [...state];
newState.pop();
return newState;
}
6. 一句话总结
React 状态不可变原则不可动摇。在 useReducer 或 redux 的 reducer 中,只能“拷贝-再修改-再返回”,千万别直接对 state 调用原地方法,否则后果难以预料!
核心记忆公式
- “reducer 修改 state,只能对副本动手!”
- “pop/push/splice/sort 等原地操作,都不能直接作用在原 state 上!”
- “多次 reducer 调用时,直接操作 state 会踩大坑!”