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
相关产品推荐
相关产品推荐

