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

React 18中useState数组添加元素失效问题排查求助

React 18 告警系统仅显示一条消息的问题排查与修复

问题描述

将旧版React(v16)的告警消息系统迁移至React 18新项目后出现异常:在useEffect中连续调用两次addAlert添加告警,最终仅显示一条,调试发现alertList最多仅包含一个元素,该系统在旧版本中可正常运行。

相关代码如下:

app.js

import "./styles.css";

import { useEffect, useState } from "react";
import Alerts from "./alerts";

export default function App() {
  const [alertList, setAlertList] = useState([]);

  function addAlert(alert) {
    const newAlertList = [...alertList, alert];
    setAlertList(newAlertList);
  }

  function removeAlert(idx) {
    const newAlertList = [...alertList];
    newAlertList.splice(idx, 1);
    setAlertList(newAlertList);
  }

  useEffect(() => {
    addAlert({ level: "success", message: `A success alert.` });
    addAlert({ level: "info", message: `An info alert.` });
  }, []);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <Alerts alertList={alertList} removeAlert={removeAlert} />
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

alerts.js

function AlertItem(alert, idx, removeAlert) {
  return (
    <div className="w-auto rounded-lg py-2 px-6 mt-2" key={idx}>
      <span>{alert.message}</span>
      <button className="ml-2 cursor-pointer" onClick={() => removeAlert(idx)}>
        x
      </button>
    </div>
  );
}

export default function Alerts(props) {
  const { alertList, removeAlert } = props;

  return (
    <div className="relative" id="alert-wrapper">
      <div className="absolute right-10 text-right">
        {alertList.length
          ? alertList.map((alert, idx) => AlertItem(alert, idx, removeAlert))
          : null}
      </div>
    </div>
  );
}

问题原因

核心是状态更新的闭包陷阱结合React 18的自动批处理机制导致:

  1. addAlert函数中,alertList是从组件渲染时的闭包中获取的。在useEffect的单次回调执行中,两次调用addAlert时,alertList始终是初始的空数组[]。
  2. React 18在非紧急更新场景(如useEffect回调)会自动批处理状态更新,两次setAlertList不会立即触发重渲染,因此第二次更新会覆盖第一次的结果,最终alertList仅保留最后一条告警。
  3. 旧版React(v16)中,useEffect内的状态更新不会被批处理,两次setState会依次触发重渲染,第二次addAlert能拿到第一次更新后的alertList,因此正常工作。

修复方案

推荐方案:使用函数式状态更新

修改addAlert和removeAlert,采用setState的函数式形式,确保每次更新都基于最新的状态值,彻底避免闭包陷阱:

function addAlert(alert) {
  // 接收prevState参数,基于最新状态生成新数组
  setAlertList(prevAlertList => [...prevAlertList, alert]);
}

function removeAlert(idx) {
  setAlertList(prevAlertList => {
    const newAlertList = [...prevAlertList];
    newAlertList.splice(idx, 1);
    return newAlertList;
  });
}

临时方案:合并单次状态更新

如果仅在useEffect中批量添加告警,可直接合并为一次状态更新,但该方案无法解决动态多次调用addAlert的场景:

useEffect(() => {
  setAlertList([
    { level: "success", message: `A success alert.` },
    { level: "info", message: `An info alert.` }
  ]);
}, []);

额外优化:避免使用索引作为key

当前代码中用数组索引作为AlertItem的key,在删除元素时可能导致组件渲染异常,建议给每个告警添加唯一ID:

// 修改addAlert,生成唯一ID
function addAlert(alert) {
  setAlertList(prev => [...prev, { ...alert, id: `${Date.now()}-${Math.random()}` }]);
}

// 修改AlertItem和removeAlert,基于ID操作
function AlertItem(alert, removeAlert) {
  return (
    <div className="w-auto rounded-lg py-2 px-6 mt-2" key={alert.id}>
      <span>{alert.message}</span>
      <button className="ml-2 cursor-pointer" onClick={() => removeAlert(alert.id)}>
        x
      </button>
    </div>
  );
}

function removeAlert(alertId) {
  setAlertList(prev => prev.filter(alert => alert.id !== alertId));
}

// Alerts组件中调用AlertItem的方式
{alertList.map((alert) => AlertItem(alert, removeAlert))}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 14:50:07