You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

useState的setState在StrictMode下行为异常的原因及解决方法咨询

useState的setState在StrictMode下行为异常的原因及解决方法咨询

兄弟,我完全懂你遇到的这个坑!StrictMode在开发环境下的重复执行机制确实容易让人懵,尤其是之前只关注过它对useEffect的影响。咱们一步步拆解问题:

问题根源:你的状态更新函数是不纯的

StrictMode在开发环境下会刻意重复调用一些纯函数(包括你传给setTodos的更新回调),目的是帮你检测代码里的不纯操作——而你的问题正好出在这儿:

你在更新状态时直接修改了prev数组里的todo对象:

const newTodos = [...prev]
const todo = newTodos.find(todo => todo.id === id)
todo.done = !todo.done // 这里直接修改了原状态对象!

虽然你浅拷贝了prev数组,但数组里的todo对象还是原来的引用,修改它的done属性属于直接修改原状态的不纯操作。

在StrictMode下,这个更新函数会被执行两次:

  1. 第一次执行:prev是当前状态,你把目标todo的done取反,返回新数组;
  2. 第二次执行:此时prev里的todo已经被第一次执行修改了(对象是引用类型),所以再次取反就又变回原来的值,返回的数组状态和初始状态一致。

这就导致你看到“setting”打印两次,最终状态又回到了点击前的样子。而第一次点击正常是因为初始状态还没被修改过,两次执行的结果刚好抵消后,React最终应用了正确的状态;但后续点击时,这种来回取反就会让状态看起来没变化。

解决方法:让更新函数成为纯函数

核心原则是永远不要直接修改原状态对象,而是创建新的对象和数组来更新状态。把你的toggleTodo函数改成这样:

const toggleTodo = id => () => {
  console.log('firing')
  setTodos(prev => {
    console.log('setting')
    // 用map生成新数组,找到目标todo时创建新对象
    return prev.map(todoItem => 
      todoItem.id === id 
        ? {...todoItem, done: !todoItem.done} 
        : todoItem
    )
  })
}

这样做的好处:

  • 每次更新都返回全新数组(map方法生成新数组);
  • 修改todo状态时,通过扩展运算符{...todoItem}创建新对象,完全不碰原状态对象;
  • 现在更新函数是纯函数:不管执行多少次,传入相同的prev都会返回相同结果,StrictMode重复执行也不会导致状态异常。

补充说明StrictMode的作用

StrictMode是React专门用来帮开发者提前发现潜在问题的工具,它只会在开发环境生效,生产环境下不会有这些重复执行的行为。遇到这种问题其实是好事——它在提醒你代码里有不纯的状态操作,这类操作在复杂场景下很容易引发难以排查的bug。

备注:内容来源于stack exchange,提问作者Pastilhas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.21 13:39:32