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

React子组件导致父组件状态项被误删的问题求助

React子组件导致父组件状态项被误删的问题求助

我的需求

我正在实现一个ListWrap组件,用来包裹消息列表里的每一项,核心功能是:

  • 检测子项的添加和移除状态
  • 即使子项从父组件的列表中被移除,页面上依然要显示所有历史项
  • 用包裹层的颜色区分状态:新增的项显示绿色,被移除的项显示红色

这是简化后的演示版本,实际有更复杂的需求,但现在先解决这个基础问题。

遇到的奇怪问题

现在的问题是:当删除的不是列表最后一项时,比如列表有3项,我点第2项的删除按钮,结果第2和第3项都被从父组件的状态里删掉了!

我排查了几个场景:

  • 不用ListWrap组件时,列表的增删逻辑完全正常,删哪一项就只删哪一项
  • 如果在ListWrap里直接返回传入的children(就是代码里注释掉的那段),也没问题
  • 但只要我在ListWrap里返回经过我处理后的子项(存在allChildrenItems状态里的那些项),就会出现这个误删后面所有项的问题

而且我发现父组件的messages状态直接被影响了,明明调用removeMessage时只过滤掉了指定id的项,结果状态里后面的项也没了,看起来是子组件的逻辑导致了这个问题,但我找不到原因。

预期行为

点击某一项的删除按钮时,应该只有那一项的包裹层变成红色(左边的彩色框),页面上依然显示所有项,父组件的状态也只应该删掉被选中的那一项。

代码示例

Test.js(父组件)

import React, { useState } from "react";
import ListWrap from "./ListWrap";

const Test = () => {
  const [messages, setMessages] = useState([]);

  const addMessage = () => {
    const id = Date.now();
    setMessages([...messages, { id: id, text: `Message ${id}` }]);
  };

  const removeMessage = (id) => {
    setMessages(messages.filter((message) => message.id !== id));
  };

  return (
    <div className="container mx-auto p-4">
      <div className="mb-4 flex justify-between">
        <button
          onClick={addMessage}
          className="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600 focus:outline-none"
        >
          Add New
        </button>
        <button
          onClick={() => setMessages(messages.slice(0, -1))}
          className="rounded bg-orange-500 px-4 py-2 text-white hover:bg-orange-600 focus:outline-none"
        >
          Remove Last
        </button>
        <button
          onClick={() => setMessages([])}
          className="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600 focus:outline-none"
        >
          Remove All
        </button>
      </div>

      <div className="space-y-2">
        <ListWrap>
          {messages.map((message) => (
            <div
              key={message.id}
              className="relative rounded bg-gray-500 p-4 text-white shadow-lg"
            >
              <p>{message.text}</p>
              <button
                onClick={() => removeMessage(message.id)}
                className="absolute right-0 top-0 mr-2 mt-2 rounded bg-red-500 px-2 py-1 text-white hover:bg-red-600 focus:outline-none"
              >
                X
              </button>
            </div>
          ))}
        </ListWrap>
      </div>
    </div>
  );
};

export default Test;

ListWrap.js(子组件)

import React, { useState, useEffect } from "react";

const ListWrap = ({ children }) => {
  const [allChildrenItems, setAllChildrenItems] = useState([]);

  useEffect(() => {
    const newChildren = React.Children.toArray(children);
    const newChildrenKeys = newChildren.map((child) => child.key);
    const currentChildrenKeys = allChildrenItems.map((item) => item.child.key);
    const addedChildrenKeys = newChildrenKeys.filter(
      (key) => !currentChildrenKeys.includes(key)
    );
    const removedChildrenKeys = currentChildrenKeys.filter(
      (key) => !newChildrenKeys.includes(key)
    );

    const updatedChildrenItems = [
      ...allChildrenItems.map((item) => {
        if (removedChildrenKeys.includes(item.child.key))
          return { ...item, isActive: false };
        return item;
      }),
      ...newChildren
        .filter((child) => addedChildrenKeys.includes(child.key))
        .map((child) => ({ child, isActive: true })),
    ];

    setAllChildrenItems(updatedChildrenItems);
  }, [children]);

  // 用这个返回方式时,会导致父组件状态误删项:
  return (
    <div>
      {allChildrenItems.map((item) => (
        <div key={item.child.key}>
          <div
            className={
              item.isActive
                ? "mb-2 pl-10 z-50 bg-green-500"
                : "mb-2 pl-10 z-50 bg-red-500"
            }
          >
            {item.child}
          </div>
        </div>
      ))}
    </div>
  );

  // 直接返回children的话,完全正常:
  // return (
  //   <>
  //     {children.map((item) => (
  //       <div key={item.key}>
  //         <div className={item.isActive ? "active" : "inactive"}>{item}</div>
  //       </div>
  //     ))}
  //   </>
  // );
};

export default ListWrap;

备注:内容来源于stack exchange,提问作者Orbitall

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 14:04:32