React列表删除项后输入状态异常:状态未保留问题求助
问题:列表删除项后输入值状态异常
我想实现一个带添加按钮和每项删除按钮的列表,以下是基础代码(项目中逻辑一致但更复杂):
App.tsx
import React, { createRef, useEffect, useState } from 'react'; import ListItem from "./list_item"; const testData = [ { value: 1, } ] type EnhancedListItem = { id: number; ref: React.RefObject<any>; item: React.ReactElement; } function App() { const [enhancedListItems, setEnhancedListItems] = useState<EnhancedListItem[]>([]); useEffect(() => { const newEnhancedListItems: EnhancedListItem[] = []; testData.forEach((t, i) => { const ref = createRef<any>(); const item = React.cloneElement(<ListItem />, { id: i, defaultValue: t.value, ref }); newEnhancedListItems.push({ id: i, ref, item }); }); setEnhancedListItems(newEnhancedListItems); }, []); function onAddListItemButtonClick(): void { const ref = createRef<any>(); const newId = enhancedListItems.reduce((p: number, c) => Math.max(p, c.id), 0) + 1; const newObject = { defaultValue: Math.floor(Math.random() * 10), id: newId } const item = React.cloneElement(<ListItem />, { ...newObject, ref }); setEnhancedListItems([...enhancedListItems, { id: newId, item, ref }]); } function onRemoveButtonClick(id: number): void { setEnhancedListItems(enhancedListItems.filter(t => t.id !== id)); } return ( <div> {enhancedListItems.map((t, i) => ( <div key={i}> <span>{t.item}</span> <span><button onClick={() => onRemoveButtonClick(t.id)}>-</button></span> </div> ))} <button onClick={onAddListItemButtonClick}>+</button> </div> ); } export default App;
ListItem.tsx
import React, { useState } from "react"; export type ListItemProps = { id?: number; defaultValue?: string; } function ListItem(props: ListItemProps, ref: React.ForwardedRef<any>): React.ReactElement { const [value, setValue] = useState(props.defaultValue ?? ""); function onInputChange(e: React.ChangeEvent<HTMLInputElement>): void { setValue(e.target.value); } return ( <div> <span>{props.id}</span> <input type="text" value={value} onChange={onInputChange} /> </div> ); } export default React.forwardRef(ListItem);
问题描述
点击某一项的删除按钮后,该项ID消失,但其他项的输入值会错乱,像是最后一项被移除,所有项的值变成前一项的值。请问为何输入值状态未被保留?是否是cloneElement流程有问题?
更新:简化复现代码
import React, { useState } from 'react'; type EnhancedListItem = { id: number; } function App() { const [enhancedListItems, setEnhancedListItems] = useState<EnhancedListItem[]>([]); function onAddListItemButtonClick(): void { const newId = enhancedListItems.reduce((p: number, c) => Math.max(p, c.id), 0) + 1; setEnhancedListItems([...enhancedListItems, { id: newId }]); } function onRemoveButtonClick(id: number): void { setEnhancedListItems(enhancedListItems.filter(t => t.id !== id)); } return ( <div> {enhancedListItems.map((t, i) => ( <div key={i}> <span>{t.id}</span> <input type="text" /> <button onClick={() => onRemoveButtonClick(t.id)}>-</button> </div> ))} <button onClick={onAddListItemButtonClick}>+</button> </div> ); } export default App;
解决方案
核心原因:错误使用数组索引作为key
React的Diff算法依赖key识别列表元素的唯一性。你当前用数组索引key={i},删除中间项后,剩余项的索引会重新排列,React会误以为新列表的索引对应原列表后续位置的组件,从而复用错误的组件实例,导致输入值错位。
另外,原代码把React元素(t.item)存在状态里是反模式,React状态应该存储数据而非渲染结果,这会导致状态更新时组件复用逻辑混乱。
修复步骤
1. 用唯一ID替换索引作为key
渲染列表时,将key={i}改为key={t.id},确保每个列表项的key唯一且稳定:
// 原代码 {enhancedListItems.map((t, i) => ( <div key={i}> ... </div> ))} // 修改后 {enhancedListItems.map((t) => ( <div key={t.id}> ... </div> ))}
2. 状态存储数据而非React元素
修改App.tsx的状态和渲染逻辑,只存储列表项数据,渲染时动态生成ListItem组件:
import React, { useState } from 'react'; import ListItem from "./list_item"; const testData = [ { id: 0, value: 1, } ] type ListItemData = { id: number; defaultValue: number; } function App() { // 状态只存储数据 const [listItems, setListItems] = useState<ListItemData[]>(testData); function onAddListItemButtonClick(): void { const newId = listItems.reduce((p: number, c) => Math.max(p, c.id), 0) + 1; const newItem = { id: newId, defaultValue: Math.floor(Math.random() * 10) }; setListItems([...listItems, newItem]); } function onRemoveButtonClick(id: number): void { setListItems(listItems.filter(t => t.id !== id)); } return ( <div> {listItems.map((item) => ( <div key={item.id}> <ListItem id={item.id} defaultValue={item.defaultValue.toString()} /> <button onClick={() => onRemoveButtonClick(item.id)}>-</button> </div> ))} <button onClick={onAddListItemButtonClick}>+</button> </div> ); } export default App;
3. 简化ListItem组件(可选)
如果不需要ref,可以去掉forwardRef,简化组件:
import React, { useState } from "react"; export type ListItemProps = { id?: number; defaultValue?: string; } function ListItem(props: ListItemProps): React.ReactElement { const [value, setValue] = useState(props.defaultValue ?? ""); function onInputChange(e: React.ChangeEvent<HTMLInputElement>): void { setValue(e.target.value); } return ( <div> <span>{props.id}</span> <input type="text" value={value} onChange={onInputChange} /> </div> ); } export default ListItem;
简化版代码修复
针对你提供的简化代码,仅修改key即可解决问题:
import React, { useState } from 'react'; type EnhancedListItem = { id: number; } function App() { const [enhancedListItems, setEnhancedListItems] = useState<EnhancedListItem[]>([]); function onAddListItemButtonClick(): void { const newId = enhancedListItems.reduce((p: number, c) => Math.max(p, c.id), 0) + 1; setEnhancedListItems([...enhancedListItems, { id: newId }]); } function onRemoveButtonClick(id: number): void { setEnhancedListItems(enhancedListItems.filter(t => t.id !== id)); } return ( <div> {enhancedListItems.map((t) => ( // 把key从i改成t.id <div key={t.id}> <span>{t.id}</span> <input type="text" /> <button onClick={() => onRemoveButtonClick(t.id)}>-</button> </div> ))} <button onClick={onAddListItemButtonClick}>+</button> </div> ); } export default App;
内容的提问来源于stack exchange,提问作者Christophe Thomas
相关产品推荐
相关产品推荐

