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

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"]
  • 第一次执行更新回调:
    1. 直接修改prev[pos],把指定位置的元素换成新水果
    2. 返回一个新数组:修改后的prev展开 + 追加新水果
  • React丢弃这次的返回值,并且因为要检测副作用,会恢复原始状态(相当于把你修改的prev还原回去)
  • 第二次执行更新回调:
    1. prev回到了初始的原始数组
    2. 返回[...prev, name],也就是只在末尾追加新水果
  • 最终状态是第二次回调的结果,所以你看到的是仅追加,没有替换指定位置

从第二次点击开始:

  • 此时状态已经是第一次点击后的数组(比如["apple", "mango", "strawberry", "newFruit"])
  • 第一次执行更新回调:
    1. 直接修改prev[pos],替换指定位置元素
    2. 返回修改后的prev展开 + 追加新水果
  • 这一次,React的状态恢复机制没起作用(因为你已经直接修改了状态引用,导致原始状态被污染)
  • 第二次执行更新回调时,prev已经是被第一次修改后的数组了
  • 返回[...prev, name],也就是替换指定位置的同时,又追加了新水果
  • 最终状态是第二次回调的结果,所以你看到既替换又追加的现象

关于日志显示异常

setArray回调里的prev日志不是最新状态,是因为Strict Mode下两次执行回调,第一次修改了prev但结果被丢弃,第二次执行时的prev可能是原始状态或者已经被污染的状态,导致日志里的prev和你预期的最新状态不一致。

内容的提问来源于stack exchange,提问作者info campus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 08:22:46