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

React 18异步代码下useState多状态更新的困惑

React 18 + TypeScript 状态更新不符合预期问题分析

代码示例

interface User {
  id: number;
  name: string;
}

function App() {
  const [users, setUsers] = useState<User[]>([]);
  const [error, setError] = useState("");

  useEffect(() => {
    axios
      .get<User[]>("https://jsonplaceholder.typicode.com/users")
      .then((res) => setUsers(res.data));
  }, []);

  const addUser = () => {
    const newUser = { id: 0, name: "Test" };
    setUsers([newUser, ...users]);
    axios
      .post("https://jsonplaceholder.typicode.com/users", newUser)
      .then((res) => {
        console.log("size", users.length);
        setUsers([res.data, ...users]);
      })
      .catch((err) => setError(err.message));
  };

  return (
    <>
      {error && <p>{error}</p>}
      <button onClick={addUser}>Add</button>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
}

export default App;

问题描述

原本预期点击一次Add按钮会触发两次重渲染:

  1. 第一次在列表开头添加id为0的Test用户并渲染;
  2. axios.post请求完成后,第二次重渲染在列表开头添加服务器返回的id为11的Test用户,最终列表会出现两个重复的Test用户。
    虽然知道React的状态批处理机制,但认为这两次更新属于不同批次,应该触发两次重渲染。但实际测试后,列表仅显示一个新增的Test用户,想理解哪里出现了偏差。

问题原因

核心问题是闭包导致的状态值捕获:

  • addUser函数在组件某次渲染时创建,函数内部的users变量捕获的是该渲染周期的状态值(也就是初始的10个用户数组)。
  • 第一次调用setUsers([newUser, ...users])时,确实会把id为0的Test加到列表开头,触发重渲染。但此时axios.post的回调函数已经创建完成,它内部引用的users还是更新前的旧数组。
  • 当请求完成后执行setUsers([res.data, ...users]),这里的users依然是初始的10个用户数组,所以这次更新会直接把id为11的Test加到旧数组前面,完全覆盖了第一次更新的结果。
  • 最终组件状态是[id11的Test, ...初始10个用户],所以只会显示一个新增的Test用户。

解决方法

使用React的函数式状态更新,它会接收当前最新的状态值作为参数,避免闭包陷阱:

const addUser = () => {
  const newUser = { id: 0, name: "Test" };
  setUsers([newUser, ...users]);
  axios
    .post("https://jsonplaceholder.typicode.com/users", newUser)
    .then((res) => {
      // 用函数式更新,拿到最新的prevUsers状态
      setUsers(prevUsers => [res.data, ...prevUsers]);
    })
    .catch((err) => setError(err.message));
};

这样处理后,请求完成时的更新会基于第一次更新后的最新数组,最终列表会显示[id11的Test, id0的Test, ...初始10个用户],符合预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 04:52:22