React setState函数式更新直接修改prevState导致计数器增量翻倍问题
异常底层原因解释
这个问题的核心是你直接修改了React传给你的不可变prevState,加上严格模式下setState更新函数会执行两次,共同导致了计数器增量翻倍,具体逻辑拆解如下:
- 首先,你现在用的React版本大概率是18及以上,官方默认创建的项目都会用
<StrictMode>严格模式包裹根组件。这个模式下,setState传入的函数式更新回调会被故意调用两次,目的是提前暴露开发者写的有副作用的不安全更新逻辑,你现在遇到的就是典型的被严格模式检测到问题的情况。 - 其次,你代码里的
prevState.counters是引用类型的数组,const counters = prevState.counters这行只是复制了数组的内存引用,并没有生成新的数组,两个变量指向的是state里存储的同一个数组对象。之后你执行counters[index] = ++counter的时候,本质是直接修改了prevState里的原始状态值,这违反了React state不可变的原则,属于有副作用的操作。 - 两次更新函数执行的过程如下:
- 第一次执行回调:拿到的prevState对应值是
x,你直接修改prevState的数组元素为x+1,返回的状态是x+1 - 第二次执行回调:因为你上一次已经把prevState的原始值改了,这次拿到的prevState对应值已经是
x+1,再次加1就变成了x+2,返回的状态是x+2
最终state会被更新为第二次返回的x+2,所以你看到每次点击都加2。
- 第一次执行回调:拿到的prevState对应值是
为什么拷贝数组就能解决问题
当你用const counters = [...prevState.counters]拷贝数组之后,你修改的是全新生成的数组对象,完全不会改动prevState里的原始值。两次执行更新回调的时候,拿到的prevState都是初始的x,两次返回的状态都是x+1,React会自动合并重复的状态更新,最终只会生效一次+1的操作,符合你的预期。
内容的提问来源于stack exchange,提问作者Ilya
相关产品推荐
相关产品推荐

