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的自动批处理机制导致:
addAlert函数中,alertList是从组件渲染时的闭包中获取的。在useEffect的单次回调执行中,两次调用addAlert时,alertList始终是初始的空数组[]。- React 18在非紧急更新场景(如
useEffect回调)会自动批处理状态更新,两次setAlertList不会立即触发重渲染,因此第二次更新会覆盖第一次的结果,最终alertList仅保留最后一条告警。 - 旧版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
相关产品推荐
相关产品推荐

