You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 03:27:01