React中更新数组的异常行为及日志问题问询
问题成因分析
先看你提供的代码:
import { useRef, useState } from "react"; import "./styles.css"; export default function App() { const [array, setArray] = useState(["apple", "mango", "strawberry"]); console.log("outside", array, Date.now()); function update(e) { const name = document.getElementById("name").value; const pos = Number(document.getElementById("pos").value); setArray((prev) => { console.log("prev ===>", prev, Date.now()); return [...prev, (prev[pos] = name)]; }); } const inputRef = useRef(); const posRef = useRef(); return ( <div className="App"> <input id="name" ref={inputRef} placeholder="fruit name" /> <input id="pos" ref={posRef} placeholder="posistion" /> <div style={{ display: "flex", justifyContent: "center", gap: "25px" }}> <div> {array.map((info, index) => ( <p key={index}>{info}</p> ))} </div> </div> <button onClick={update}>UPDATE</button> </div> ); }
出现这种奇怪行为的核心原因有两个:违反React状态不可变原则 + Strict Mode的开发环境特性
1. 直接修改了React的状态快照prev
在setArray的回调里,你写了prev[pos] = name——这是直接修改了React传递给你的状态快照prev。React的状态设计是不可变的,你应该始终返回一个新的状态对象,而不是修改现有的状态引用。
这里的prev[pos] = name是赋值表达式,它的返回值是name,所以你的回调等价于:
setArray((prev) => { // 直接修改了prev(当前状态的引用) prev[pos] = name; // 返回一个新数组:把修改后的prev展开,再追加name return [...prev, name]; });
2. Strict Mode的两次执行机制
React在开发环境的Strict Mode下,会刻意重复执行状态更新器(也就是setState的回调函数)两次,目的是检测你代码里的意外副作用(比如直接修改状态、操作DOM等)。第一次执行的结果会被丢弃,第二次的结果才会被应用到状态中。
现在结合这两点拆解你的现象:
第一次点击UPDATE按钮:
- 初始状态数组:
["apple", "mango", "strawberry"] - 第一次执行更新回调:
- 直接修改
prev[pos],把指定位置的元素换成新水果 - 返回一个新数组:修改后的
prev展开 + 追加新水果
- 直接修改
- React丢弃这次的返回值,并且因为要检测副作用,会恢复原始状态(相当于把你修改的
prev还原回去) - 第二次执行更新回调:
prev回到了初始的原始数组- 返回
[...prev, name],也就是只在末尾追加新水果
- 最终状态是第二次回调的结果,所以你看到的是仅追加,没有替换指定位置
从第二次点击开始:
- 此时状态已经是第一次点击后的数组(比如
["apple", "mango", "strawberry", "newFruit"]) - 第一次执行更新回调:
- 直接修改
prev[pos],替换指定位置元素 - 返回修改后的
prev展开 + 追加新水果
- 直接修改
- 这一次,React的状态恢复机制没起作用(因为你已经直接修改了状态引用,导致原始状态被污染)
- 第二次执行更新回调时,
prev已经是被第一次修改后的数组了 - 返回
[...prev, name],也就是替换指定位置的同时,又追加了新水果 - 最终状态是第二次回调的结果,所以你看到既替换又追加的现象
关于日志显示异常
setArray回调里的prev日志不是最新状态,是因为Strict Mode下两次执行回调,第一次修改了prev但结果被丢弃,第二次执行时的prev可能是原始状态或者已经被污染的状态,导致日志里的prev和你预期的最新状态不一致。
内容的提问来源于stack exchange,提问作者info campus
相关产品推荐
相关产品推荐

