React函数组件中子组件触发onDelete时使用旧state的问题及解决方法
问题原因与解决方案
核心原因
- 闭包捕获旧状态:
onDelete函数在组件每次渲染时被创建,当你把<Item>元素存入tdList状态时,这个Item绑定的是当时的onDelete版本——它捕获了创建时的tdList状态。后续组件更新(比如添加新Item)后,旧Item里的onDelete依然引用的是旧的tdList,自然拿不到最新数据。 - 错误的状态存储方式:把JSX元素直接存在React状态里是反模式。状态应该存数据(比如待办项的id、文本),而不是UI元素。这种做法不仅会引发闭包问题,还会让状态管理变得混乱。
- 直接修改原状态数组:
splice是直接修改原数组的方法,React要求状态是不可变的——直接修改原数组不会触发组件重渲染,而且你用setTD(tdList.splice(...))的写法完全错误,splice返回的是被删除的元素,不是新数组。
解决步骤
- 重构状态存储:让
tdList存储待办项的数据对象,而不是<Item>组件。比如每个项是{ id: number, text: string }的结构。 - 使用函数式更新获取最新状态:在
onDelete和onHandleSubmit里,用setTD(prevState => newState)的形式,这样能确保拿到最新的状态值,避免闭包陷阱。 - 用不可变方式修改数组:删除项时用
filter创建新数组,添加项时展开旧数组并追加新数据对象,不要直接修改原数组。 - 渲染时生成UI组件:在组件的渲染逻辑里,通过
map遍历tdList数据,动态生成<Item>组件,这样每个Item绑定的都是最新的onDelete函数。
修改后的代码示例
import { useState, useRef } from 'react'; function ToDo() { // 状态存储待办数据,而非JSX元素 const [tdList, setTD] = useState([]); const [item, setItem] = useState(""); // 用ref存储自增ID,避免普通变量在重渲染时重置 const itemsAdded = useRef(0); const onDelete = (id) => { // 函数式更新,拿到最新的状态 setTD(prevTDList => prevTDList.filter(todo => todo.id !== id)); }; const onHandleSubmit = (event) => { event.preventDefault(); if (!item.trim()) return; // 过滤空内容 // 函数式更新追加新待办数据 setTD(prevTDList => [ ...prevTDList, { id: itemsAdded.current, text: item } ]); setItem(''); itemsAdded.current++; }; return ( <div> <form onSubmit={onHandleSubmit}> <input type="text" value={item} onChange={(e) => setItem(e.target.value)} /> <button type="submit">添加</button> </form> <div> {/* 渲染时动态生成Item组件 */} {tdList.map(todo => ( <Item key={todo.id} id={todo.id} item={todo.text} delete={onDelete} /> ))} </div> </div> ); } function Item({ id, item, delete: onDelete }) { return ( <div> <span>{item}</span> <button onClick={() => onDelete(id)}>删除</button> </div> ); }
额外说明
- 用
useRef存储itemsAdded,因为普通变量在组件重渲染时会被重置,而ref的值在组件生命周期内保持不变。 - 给每个动态生成的
<Item>加上key属性,这是React列表渲染的要求,能提升性能并避免渲染错误。
内容的提问来源于stack exchange,提问作者MDrapic
相关产品推荐
相关产品推荐

