React JS TodoList新增元素时已标记LI颜色不跟随移动问题咨询
故障原因
- 你给列表项绑定的
key用了数组索引:React依赖key判断列表元素的唯一性,你每次向数组头部插入新元素时,所有旧条目的索引都会+1,React会错误地把旧索引位置对应的子组件状态(比如完成态的红色背景)直接复用给新位置的条目,就出现了颜色不跟着条目移动的问题。 - 直接修改了state原值:调用
items.unshift()、delete items[index]都是直接修改原state数组,违反了React state的不可变更新原则,会导致渲染异常。 - 完成状态存放在子组件内部:子组件内部的
color状态和父组件的待办条目没有绑定关系,列表顺序变化时状态和条目自然错位。 - 冗余的
currentMap状态:没必要将遍历生成的组件列表单独存为state,直接在渲染时遍历items即可,额外增加了状态同步成本。
修复方案
步骤1:改造待办项结构,将完成状态提到父组件管理
每个待办项不再是纯字符串,改为包含唯一id、内容、完成状态的对象,id用时间戳生成,避免使用索引作为key。
步骤2:所有state更新遵循不可变原则
新增、删除待办都返回新数组,不修改原state值。
步骤3:删除冗余的currentMap状态,直接在渲染时遍历生成列表
步骤4:子组件改为纯展示组件,接收父组件传递的完成状态和修改回调,不内部维护状态
修改后完整代码
App.js
import React, { useState, useRef } from "react" import ItemToDo from "./ItemToDo" import "./style.scss" export default function App() { // 每个待办项结构:{id: 唯一标识, content: 内容, isDone: 是否完成} const [items, setItems] = useState([]) const inputRef = useRef() const addElement = () => { const inputVal = inputRef.current.value.trim() if (inputVal === "") { alert("Please enter something...") return } // 不可变更新:返回新数组,头部插入新待办项 setItems(prev => [ { id: Date.now(), // 用时间戳作为唯一id content: inputVal, isDone: false }, ...prev ]) inputRef.current.value = "" } const removeElement = (id) => { // 不可变更新:过滤掉对应id的项,返回新数组 setItems(prev => prev.filter(item => item.id !== id)) } // 修改待办完成状态的方法 const toggleDone = (id) => { setItems(prev => prev.map(item => { if(item.id === id) { return {...item, isDone: true} } return item })) } const handleKey = (e) => { if (e.key === 'Enter') { addElement() } } return ( <> <h1>ToDo List</h1> <input type="text" placeholder="Name here" ref={inputRef} id="elementName" onKeyDown={handleKey} autoFocus /> <input type="button" value="Add Item" id="submit" onClick={addElement} /> <div> <ul> {/* 直接遍历生成列表,key用唯一id */} {items.map(item => <ItemToDo key={item.id} item={item} removeElement={() => removeElement(item.id)} markDone={() => toggleDone(item.id)} /> )} </ul> </div> </> ) }
ItemToDo.js
import React from "react" // 子组件不再内部维护状态,所有状态和回调从父组件传入 export default function ItemToDo({item, removeElement, markDone}) { return ( <li style={{backgroundColor: item.isDone ? "red" : "green"}}> <span>{item.content}</span> <input type="button" value="Delete" onClick={removeElement} /> <input type="button" value="Done!" onClick={markDone} /> </li> ) }
内容的提问来源于stack exchange,提问作者Narcis Lazar
相关产品推荐
相关产品推荐

