React初体验:如何实现父子组件通信

101 阅读5分钟

前言

在React开发中,组件通信是实现复杂功能的核心技能,通过组件化思想,我们可以将页面拆分为多个独立的组件,每个组件专注于自己的功能,同时通过数据传递与协作完成整体业务逻辑。

本文将以一个简单的 TodoList 项目为例,结合代码解析,详细讲解React组件通信的实现方式,并划出重要知识点。


一、项目结构与组件划分

1. 项目目录结构

image.png

2. 组件通信的层级关系

  • 父组件TodoList(负责管理状态和逻辑)
  • 子组件TodoForm(输入表单)和 Todos(列表展示)
  • 通信方向
    • 父组件通过 props 向子组件传递数据和回调函数
    • 子组件通过回调函数将数据或事件传递回父组件

二、父子组件通信实例解析

1. 父组件 TodoList.jsx

import '../Todo.css'
import TodoForm from './TodoForm'
import Todos from './Todos'
import { useState } from 'react'

function TodoList() {
  // 使用 useState 管理状态
  const [todos, setTodos] = useState([
    { id: 1, text: '脱单', completed: false },
    { id: 2, text: '摆烂', completed: false },
    { id: 3, text: '国泰民安', completed: false }
  ])

  // 添加新任务的回调函数
  const handleAdd = (text) => {
    setTodos([
      ...todos,
      {
        id: todos.length + 1, // 动态生成唯一ID
        text,
        completed: false
      }
    ])
  }

  return (
    <div className='container'>
      <h3 className='title'>TodoList</h3>
      {/* 将 handleAdd 回调函数传递给子组件 TodoForm */}
      <TodoForm onAdd={handleAdd} />
      {/* 将 todos 数据传递给子组件 Todos */}
      <Todos todos={todos} />
    </div>
  )
}

export default TodoList

关键知识点解析

  1. 状态管理(useState)
  • useState 是 React 的 Hook 函数,用于在函数组件中管理状态。

  • const [todos, setTodos] = useState([...]):

    • todos:当前状态值,存储任务列表。
    • setTodos:更新状态的方法。
  • 初始值是一个包含 3 个任务对象的数组,每个任务对象有 id(唯一标识)、text(任务内容)、completed(是否完成)三个属性。

  1. 父子通信:父组件传递回调函数给子组件
  • <TodoForm onAdd={handleAdd} />:

    • 渲染子组件 TodoForm

    • 通过 props 传递 onAdd 回调函数,子组件通过 props.onAdd 调用它。

  • <Todos todos={todos} />:

    • 渲染子组件 Todos
    • 通过 props 传递 todos 数据,子组件通过 props.todos 访问。
  • handleAdd 是一个回调函数,用于添加新任务。

  • text 是从子组件 TodoForm 传递过来的新任务内容。

  • setTodos([...todos, newTask])

    • 使用扩展运算符 ...todos 保留原有的任务列表。
    • 添加新的任务对象 newTask,其 id 通过 todos.length + 1 动态生成。
    • completed: false 表示新任务默认未完成。
  1. 数据传递:父组件向子组件传递数据
    • todos={todos}:父组件将状态 todos 作为 props 传递给子组件 Todos,子组件通过 props.todos 访问数据。

2. 子组件 TodoForm.jsx

import { useState } from "react"

function TodoForm(props) {
  const [text, setText] = useState('') // 输入框的状态管理

  // 表单提交事件处理
  const handleSubmit = (e) => {
    e.preventDefault() // 阻止默认提交行为
    props.onAdd(text) // 调用父组件传递的回调函数,传递输入内容
    setText('') // 清空输入框
  }

  // 输入框内容变化事件处理
  const handleChange = (e) => {
    setText(e.target.value) // 更新输入框状态
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="text" 
        placeholder="请输入事件" 
        value={text} 
        onChange={handleChange}
      />
      <button type="submit">添加</button>
    </form>
  )
}

export default TodoForm

关键知识点解析

  1. 子组件接收父组件传递的回调函数

    • function TodoForm(props)

      定义函数组件 TodoForm,接收父组件传递的 props(包括 onAdd 回调函数)。

    • props.onAdd(text)

      子组件通过 props 接收父组件传递的 onAdd 函数,并在表单提交时调用它,将输入内容 text 传递回父组件。

  2. 受控组件

  • <form onSubmit={handleSubmit}>: 表单提交时触发 handleSubmit 函数。

  • <input ... />:

    • value={text}: 输入框的值由 React 状态驱动(受控组件)。
    • onChange={handleChange}: 输入框内容变化时触发 handleChange 函数。
  • <button type="submit">添加</button>: 提交按钮。

  1. 事件绑定与阻止默认行为
  • handleSubmit 是表单提交的事件处理函数。
  • e.preventDefault(): 阻止表单默认的提交行为(防止页面刷新)。
  • props.onAdd(text): 调用父组件传递的 onAdd 回调函数,将当前输入内容 text 传递给父组件。
  • setText(''): 清空输入框。

3. 子组件 Todos.jsx

function Todos(props) {
  const todos = props.todos // 接收父组件传递的 todos 数据

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li> // 根据 todos 数据渲染列表
      ))}
    </ul>
  )
}

export default Todos

关键知识点解析

  1. 子组件接收父组件传递的数据
  • props.todos:子组件通过 props 接收父组件传递的 todos 数据,并直接使用它渲染列表。
  1. 列表渲染与 key 唯一性
  • todos.map(todo => (...)):

    • 遍历 todos 数组,为每个任务生成一个 <li> 元素。
    • key={todo.id}: 为每个列表项分配唯一的 key,帮助 React 高效更新和识别列表项。
    • {todo.text}: 显示任务内容。

三、组件通信的核心原则

1. 单向数据流

React 的数据流动是单向的,遵循以下规则:

  1. 父组件通过 props 向子组件传递数据。
  2. 子组件不能直接修改父组件的状态,而是通过回调函数传递数据或事件。
  3. 父组件根据子组件传递的数据更新状态,从而触发重新渲染。

示例场景

  • TodoForm 中,用户输入内容后点击“添加”,触发 handleSubmit,调用 props.onAdd(text)
  • 父组件 TodoList 接收到 text 后,通过 setTodos 更新状态,导致整个组件树重新渲染。

2. 状态提升(Lifting State Up)

当多个组件需要共享状态时,应将状态提升到它们的共同父组件中。

  • 场景TodoForm 需要向 Todos 提交数据,且 Todos 需要根据数据更新。
  • 解决方案:将状态 todos 放在父组件 TodoList 中,通过 props 分发给子组件。

3. 模块化与组件复用

  • 模块化:每个组件是一个独立的模块,专注于单一职责(如 TodoForm 负责输入,Todos 负责渲染)。
  • 组件复用:通过合理拆分组件,可以在其他项目或页面中复用这些组件(例如,将 TodoForm 改造成通用表单组件)。

四、总结与学习建议

1. 组件通信的核心方法

  • 父组件向子组件传递数据:通过 props
  • 子组件向父组件传递数据:通过回调函数。
  • 状态管理:使用 useStateuseReducer 管理组件状态。

2. 实践建议

  • 保持组件小而专注:每个组件只负责一个功能。
  • 合理拆分组件:根据业务逻辑划分组件,避免过度嵌套。
  • 优先使用受控组件:表单组件的状态由React管理,便于数据同步。

3. 扩展学习

  • Context API:解决跨层级组件通信问题。
  • Redux/Vuex:在大型项目中集中管理全局状态。
  • 自定义Hook:封装可复用的逻辑,提升代码可维护性。