千万别踩坑!React useReducer 中数组操作为何绝不能直接 pop?全面解析与最佳实践

86 阅读2分钟

在用 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;
    
  • 其他像 pushunshiftsplice 等也要同理。


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 会踩大坑!”