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

React渲染可编辑组件数组时删除功能异常问题求助

问题描述

我实现了一个支持contentEditable的组件EditableComponent,代码如下:

EditableComponent.js

const EditableComponent = (props) => {
    return <p contentEditable>{props.children}</p>;
};

在ParentComponent中,我用useState维护一个组件数组someArr,点击Add按钮可向数组中添加EditableComponent,随后将数组及setSomeArr通过props传入AllEditable组件渲染:

ParentComponent.js

import EditableComponent from "./components";
import AllEditable from "./components";

const ParentComponent = () => {
    
    const [someArr, setSomeArr] = useState([]);

    const handleNewEditable = () => {
        setSomeArr((prevContentArr) => {
            return [...prevContentArr, <EditableComponent />];
        });
    };

    return (
         <div className="wrapper">
             <AllEditable someArr={someArr} setSomeArr={setSomeArr} />
             <div>
                 <button onClick={handleNewEditable}>Add</button>
             </div>
         </div>
    );
};

AllEditable组件中,每个渲染的EditableComponent旁有一个显示'X'的span,点击应删除对应的组件:

AllEditable.js

const AllEditable= (props) => {
    const deleteContentHandler = (index) => {
        props.setSomeArr((prevState) => {
            return prevState.filter((_, idx) => idx !== index);
        });
    };

    return (
        <div>
            {props.someArr.map((content, idx) => {
                return (
                    <div key={`content-${idx}`}>
                        <span onClick={() => {deleteContentHandler(idx);}}>
                            X
                        </span>
                        <div>{content}</div>
                    </div>
                );
            })}
        </div>
    );
};

核心问题

无论点击哪个组件的删除按钮,总是会删除最后一个组件(开发者工具的Components面板中也呈现此现象)。删除逻辑(filter)本身是正确的——当移除contentEditable属性,并为每个组件添加唯一随机文本后,功能恢复正常。

已尝试的解决方法

  • 创建不包含目标组件的新数组
  • 将组件嵌套在带index键的对象中存入someArr,例如:{idx: 0, content: <EditableComponent />}
  • 为每个EditableComponent添加双击事件,切换contentEditable属性的true/false状态
  • 将EditableComponent中的元素替换为<textarea></textarea>替代<p contentEditable></p>

解决方案

问题根源

问题出在用数组索引作为React元素的key,再结合contentEditable的特性导致的:
React会复用带有相同key的组件实例来优化渲染,而contentEditable元素的内容是直接在DOM层面修改的,React不会跟踪这些DOM变化。当删除中间元素后,数组索引重新排序,React会把原末尾组件的实例复用给新的索引位置,但DOM里的contentEditable内容还保留着原状态,视觉上就表现为“删除了最后一个组件”。

解决步骤

  1. 给每个新增元素生成唯一标识,替代索引作为key
    不要直接存储组件实例,改为存储包含唯一id的对象结构。可以用uuid生成唯一id(需先安装npm install uuid):

    import { v4 as uuidv4 } from 'uuid';
    
    const handleNewEditable = () => {
        setSomeArr((prevContentArr) => {
            return [...prevContentArr, { id: uuidv4(), component: <EditableComponent /> }];
        });
    };
    
  2. 修改AllEditable的渲染与删除逻辑
    使用唯一id作为组件key,删除时通过id匹配目标元素:

    const AllEditable= (props) => {
        const deleteContentHandler = (itemId) => {
            props.setSomeArr((prevState) => {
                return prevState.filter(item => item.id !== itemId);
            });
        };
    
        return (
            <div>
                {props.someArr.map((item) => {
                    return (
                        <div key={item.id}>
                            <span onClick={() => deleteContentHandler(item.id)}>
                                X
                            </span>
                            <div>{item.component}</div>
                        </div>
                    );
                })}
            </div>
        );
    };
    
  3. 无第三方包替代方案
    不想引入uuid的话,可用时间戳+随机数生成唯一id:

    const handleNewEditable = () => {
        setSomeArr((prevContentArr) => {
            const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
            return [...prevContentArr, { id: uniqueId, component: <EditableComponent /> }];
        });
    };
    

为什么移除contentEditable就正常?

没有contentEditable时,组件内容由React状态管理,即使复用组件实例,React也会同步更新内容与数据,不会出现视觉与逻辑不符的情况;而contentEditable绕过了React的状态控制,直接操作DOM,就暴露了key复用的问题。

内容的提问来源于stack exchange,提问作者001

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 23:20:44