动态输入删除后新增时已删项重现,key含空格是否影响?
动态输入框删除后新增异常的问题解决
1. 用配料名称当key确实是核心问题之一
React依赖key识别列表组件,确保正确复用和更新。如果用ing.name作为key:
- 一旦出现名称重复的配料,key会重复,React会把它们当成同一个组件处理,删除后新增时直接复用旧组件状态,导致刚删除的项“重新出现”。
- 就算名称不重复,删除操作改变数组顺序后,索引对应的key和组件映射关系会混乱,React的diff算法无法正确识别组件增删,反而复用旧组件引发显示异常。
解决方法:给每个配料项添加唯一ID,比如用时间戳生成,修改state结构为包含唯一标识的对象:
// 初始化state const [savedingredients, setIngredients] = useState([]); // 新增时添加唯一id const saveIngredient = async () => { const name = document.getElementById("ing_name1").value; reset({ ...getValues(), ing_name1: "" }); // 用Date.now()生成唯一id,也可以用uuid库 const newIngredient = { id: Date.now(), name: name }; setIngredients([...savedingredients, newIngredient]); };
渲染时用ing.id作为key:
<ListItem key={ing.id}>
2. 直接修改原state数组导致状态更新异常
在saveIngredient里,var ingredients_array = savedingredients;是直接引用原state数组,然后push修改原数组——这违反了React状态不可变的原则,会导致React无法检测到状态变化,即使后面创建了新数组,原state已经被污染,容易引发不可预测的渲染问题。
解决方法:始终创建原数组的副本再修改,避免直接操作原state:
// 错误写法(直接修改原state) // var ingredients_array = savedingredients; // ingredients_array.push({ name: name }); // 正确写法(创建副本后修改) const newArray = [...savedingredients, { id: Date.now(), name: name }]; setIngredients(newArray);
3. 删除逻辑依赖索引的隐患
当前deleteIngredient用索引过滤项,结合key不唯一的问题,会导致数组变化后索引对应的项和预期不符。改成用唯一ID删除更可靠:
const deleteIngredient = (itemId) => { // 用filter过滤掉对应id的项,比map更简洁 const updatedIngredients = savedingredients.filter(ing => ing.id !== itemId); setIngredients(updatedIngredients); enqueueSnackbar("Ingredient Deleted", { variant: "success" }); };
调用时传入当前项的id:
<IconButton onClick={() => deleteIngredient(ing.id)}>
4. React Hook Form的Controller名称优化
当前用name={ing${i + 1}},当数组项删除后,后续项的索引变化会导致name改变,和react-hook-form的表单状态不匹配。改成用唯一ID作为name,确保每个输入框的标识唯一:
<Controller name={`ing_${ing.id}`} // 用唯一id作为name defaultValue={ing.name} control={control} rules={{ required: true, minLength: 3 }} render={({ field }) => ( <TextField variant="outlined" fullWidth id={`ing_${ing.id}`} inputProps={{ type: "text", disabled: true }} {...field} /> )} />
修改后的核心代码片段
import { useState } from 'react'; const [savedingredients, setIngredients] = useState([]); const saveIngredient = async () => { const name = document.getElementById("ing_name1").value; reset({ ...getValues(), ing_name1: "" }); const newIngredient = { id: Date.now(), name: name }; setIngredients([...savedingredients, newIngredient]); }; const deleteIngredient = (itemId) => { const updatedIngredients = savedingredients.filter(ing => ing.id !== itemId); setIngredients(updatedIngredients); enqueueSnackbar("Ingredient Deleted", { variant: "success" }); }; // 渲染部分 <List> {savedingredients.map((ing) => ( <ListItem key={ing.id}> <Grid container spacing={1}> <Grid item xs={6} md={6}> <Controller name={`ing_${ing.id}`} defaultValue={ing.name} control={control} rules={{ required: true, minLength: 3 }} render={({ field }) => ( <TextField variant="outlined" fullWidth id={`ing_${ing.id}`} inputProps={{ type: "text", disabled: true }} {...field} /> )} /> </Grid> <Grid item xs={3} md={2}> <IconButton onClick={() => deleteIngredient(ing.id)} aria-label="close" sx={{ position: "absolute", right: 8, top: 8, color: (theme) => theme.palette.grey[500] }} edge="end" > <CloseIcon /> </IconButton> <IconButton onClick={(e) => editIngredient(e, ing.id)} // 编辑逻辑也建议用id而非索引 aria-label="toggle action" sx={{ position: "absolute", right: 30, top: 8, color: (theme) => theme.palette.grey[500] }} edge="start" > {stepAction == "Edit" ? <Edit /> : <Save />} </IconButton> </Grid> </Grid> </ListItem> ))} </List>
内容的提问来源于stack exchange,提问作者Faiza
相关产品推荐
相关产品推荐

