React 中如何跨组件基于_id实现共享数组的增删改操作
跨组件状态修改的实现方案
核心原则
React 中兄弟组件需要共享、操作同一份数据时,必须把状态提升到两个兄弟组件的最近共同父组件中,所有的状态修改逻辑都写在父组件里,再通过props把数据和修改方法分别下发给子组件。
现有代码存在几个新手常见的问题,会导致逻辑跑不通,我们逐个修正:
- 在FirstChild里把父组件传入的props同步存到自身state,属于反模式:父组件状态更新时,子组件不会重新同步state,渲染结果永远是旧数据
- SecondChild没有拿到父组件的状态修改方法,根本没有触发状态变更的入口
- FirstChild的map渲染存在变量大小写错误:map参数定义为大写的
Object,渲染时却用小写的object,代码直接报引用错误 - 输入框没有绑定受控状态,拿不到用户输入的操作参数
- 按钮设置
type=Submit但没有配套form表单,点击会触发浏览器默认页面刷新,清空所有状态
分步修正代码
1. 父组件Parent:承载共享状态、定义修改方法
把数组状态、删除方法、更新方法都写在父组件中,通过props分别下发给两个子组件:
import React from "react"; import initialArrayData from "./ArrayComponent"; import FirstChild from "./FirstChild"; import SecondChild from "./SecondChild"; class Parent extends React.Component { constructor(props) { super(props); this.state = { arrayData: initialArrayData, collapsed: false }; // 绑定方法的this指向,避免调用时丢失上下文 this.handleDelete = this.handleDelete.bind(this); this.handleUpdate = this.handleUpdate.bind(this); } // 删除指定_id的数组条目 handleDelete(targetId) { this.setState(prevState => ({ arrayData: prevState.arrayData.filter(item => item._id !== Number(targetId)) })); } // 按_id判断是更新已有条目,还是新增条目 handleUpdate(targetId, newTitle) { this.setState(prevState => { const idNum = Number(targetId); const existIndex = prevState.arrayData.findIndex(item => item._id === idNum); let newArray; if (existIndex > -1) { // id存在:替换对应条目的title newArray = [...prevState.arrayData]; newArray[existIndex] = { _id: idNum, title: newTitle }; } else { // id不存在:把新条目追加到数组末尾 newArray = [...prevState.arrayData, { _id: idNum, title: newTitle }]; } return { arrayData: newArray }; }); } render() { const { arrayData } = this.state; return ( <> {/* 把数组数据传给FirstChild做渲染 */} <FirstChild data={arrayData} /> {/* 把状态修改方法传给SecondChild做操作触发 */} <SecondChild onDelete={this.handleDelete} onUpdate={this.handleUpdate} /> </> ); } } export default Parent;
2. 渲染组件FirstChild:去掉冗余state,直接读props渲染
删除子组件内部存数组的冗余逻辑,直接从父组件的props拿数据渲染,修正变量大小写问题:
import React from "react"; class FirstChild extends React.Component { render() { const { data } = this.props; const renderArray = data.slice(0, 5).map(item => { // 列表项必须带唯一key属性 return <h1 key={item._id}>{item._id} - {item.title}</h1> }) return ( <> {renderArray} </> ); } } export default FirstChild;
3. 操作组件SecondChild:绑定输入值,调用父组件方法
用组件内的临时state存用户在输入框里填的操作参数,触发按钮点击时调用父组件传下来的修改方法:
import React, { useState } from "react"; const SecondChild = ({ onDelete, onUpdate }) => { // 存删除操作的目标_id值 const [deleteId, setDeleteId] = useState(""); // 存更新/新增操作的参数 const [updateId, setUpdateId] = useState(""); const [updateTitle, setUpdateTitle] = useState(""); return ( <> <input type="text" placeholder="输入要删除的_id值" value={deleteId} onChange={(e) => setDeleteId(e.target.value)} /> <button type="button" onClick={() => onDelete(deleteId)} > 删除 </button> <br /> <input type="text" placeholder="输入条目_id" value={updateId} onChange={(e) => setUpdateId(e.target.value)} /> <input type="text" placeholder="输入条目title" value={updateTitle} onChange={(e) => setUpdateTitle(e.target.value)} /> <button type="button" onClick={() => onUpdate(updateId, updateTitle)} > 更新/新增 </button> </> ); } export default SecondChild;
关键注意事项
- 永远不要把父组件传入的props直接同步到子组件的state里做渲染源,只要父组件状态更新,直接从props读值就能拿到最新数据,存到state里会导致数据不同步
- 修改数组/对象类型的state时,绝对不要直接修改原引用,必须返回一个新的数组/对象,否则React检测不到状态变化,不会触发重渲染
- 遍历数组渲染列表时,必须给每个渲染项加上唯一的
key属性,避免渲染错乱 - 没有配套form表单的按钮,不要设置
type=Submit,否则点击会触发浏览器默认刷新行为,清空所有React状态
内容的提问来源于stack exchange,提问作者Abdul Wahab
相关产品推荐
相关产品推荐

