React父子组件如何正确传递props及拆分组件?
代码问题修正与组件拆分说明
你的import路径写法本身没有问题,核心错误集中在组件定义方式、props传递逻辑、state更新规范三个方面,以下是具体修正方案:
现存核心错误
Rows.jsx组件写法混淆了函数组件和类组件的规则:你定义的是普通函数组件,却在内部写了类组件专属的render()方法,还试图通过this访问state/上下文——函数组件没有独立的this实例,这种写法直接会报语法错误。- 父子组件传参逻辑断裂:战斗单位列表数据、修改/删除条目的事件逻辑全在父组件
DynamicTable中,你渲染子组件时没有传递任何所需参数,子组件自然拿不到数据也触发不了逻辑。 - state更新不符合React规范:直接对state中的数组执行
push/splice操作属于直接修改原状态,不会触发React重渲染;同时修改战斗单位属性的逻辑没有区分字段,会直接把整条数据覆盖为输入的字符串。 - JSX语法错误:列表项key的拼接没有包裹在JSX表达式大括号中,新增表单的表格结构不符合HTML规范(
th直接嵌套在td下是无效结构)。 - 关于事件封装:修改全局状态的逻辑不需要下沉到子组件,谁持有状态谁负责修改逻辑,子组件只需要接收回调、在交互时触发即可,这是React组件拆分的通用原则。
修正后代码
Rows.jsx(纯展示型函数组件)
import React from 'react'; // 函数组件直接通过参数接收父组件传入的props,不需要render方法,直接返回JSX function RenderRows({ combatants, onCombatantChange, onCombatantDelete }) { return ( <> {combatants.map((item, index) => ( <tr key={`combatant-${index}`}> <td> <input type="text" value={item.initiative} onChange={(e) => onCombatantChange(index, 'initiative', e)} /> </td> <td> <input type="text" value={item.name} onChange={(e) => onCombatantChange(index, 'name', e)} /> </td> <td> <button onClick={() => onCombatantDelete(index)}> Delete </button> </td> </tr> ))} </> ) } export default RenderRows;
DynamicTable.jsx(持有状态的父组件)
import React, { Component } from 'react'; import RenderRows from './Rows'; export default class DynamicTable extends Component { constructor(props) { super(props); this.state = { initiative: "", name: "", combatants: [] } // 统一在构造函数绑定this,避免render中重复生成新函数 this.updateInitiative = this.updateInitiative.bind(this); this.updateName = this.updateName.bind(this); this.handleAddCombatant = this.handleAddCombatant.bind(this); this.handleCombatantChange = this.handleCombatantChange.bind(this); this.handleCombatantDelete = this.handleCombatantDelete.bind(this); } updateInitiative(event) { this.setState({ initiative: event.target.value }); } updateName(event) { this.setState({ name: event.target.value }); } handleAddCombatant() { // 生成新数组,不直接修改原state const newCombatants = [ ...this.state.combatants, { initiative: this.state.initiative, name: this.state.name, } ]; this.setState({ combatants: newCombatants, initiative: "", name: "", }); } handleCombatantChange(index, field, event) { // 遍历生成新数组,仅修改对应条目的对应字段 const newCombatants = this.state.combatants.map((item, i) => { if (i === index) { return { ...item, [field]: event.target.value } } return item; }) this.setState({ combatants: newCombatants }); } handleCombatantDelete(index) { // 用filter过滤掉要删除的项,生成新数组 const newCombatants = this.state.combatants.filter((_, i) => i !== index); this.setState({ combatants: newCombatants }); } render() { return ( <div> New Combatant <table> <tbody> <tr> <th>Initiative</th> <th>Name</th> <th>Operation</th> </tr> <tr> <td> <input type="text" value={this.state.initiative} onChange={this.updateInitiative} /> </td> <td> <input type="text" value={this.state.name} onChange={this.updateName} /> </td> <td> <button onClick={this.handleAddCombatant}> Add Combatant </button> </td> </tr> </tbody> </table> Current Combatants <table> <thead> <tr> <th>Initiative</th> <th>Name</th> <th>Delete</th> </tr> </thead> <tbody> {/* 子组件标签上直接加属性传props即可 */} <RenderRows combatants={this.state.combatants} onCombatantChange={this.handleCombatantChange} onCombatantDelete={this.handleCombatantDelete} /> </tbody> </table> </div> ); } }
拆分原则总结
- 状态归属原则:哪个组件需要用到状态、哪个组件负责修改状态,就把状态放在对应组件中。子组件如果需要修改父组件的状态,父组件把修改函数作为props传给子组件即可,不需要把状态逻辑下沉。
- 组件粒度原则:像
RenderRows这种纯列表展示的组件,不需要维护自己的state,只做UI渲染和事件透传,属于无状态组件,用函数组件写最简洁。 - 状态更新原则:永远不要直接修改state中的对象/数组,始终创建新的引用再调用
setState,否则会出现状态变了但页面不更新的问题。 - JSX语法原则:所有JS中的变量、表达式放在JSX里时,都要用
{}包裹;HTML结构要符合规范,避免出现无效嵌套。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

