React 组件如何高效沟通?这几种方式你必须掌握

136 阅读5分钟

React 组件通信全面指南

在构建React应用时,组件间的通信是一个核心概念。无论是父子组件间的数据传递,还是跨层级的复杂数据共享,了解如何有效地实现这些通信方式是至关重要的。接下来,我们将详细介绍几种主要的组件通信方法。

一、父传子组件通讯

父传子:是最直接的一种通信方式,通过props将数据从父组件传递给子组件。props可以是基本数据类型(如字符串、数字)、对象、数组、函数,甚至是JSX元素。

父组件

  • 父组件通过 <Child list={list} /> 将 list 作为 prop 传递给子组件
export default function Parent() {
  // 使用useState钩子定义一个状态变量list和它的更新函数setList
  const [list, setList] = useState(['html', 'css', 'js'])
  const inputRef = useRef(null)
  const handler = () => {
    // 新数组包含原list的所有元素(...list展开运算符)和input输入框的当前值
    setList([...list, inputRef.current.value])
    inputRef.current.value = ''
  }

  return (
    <div>
      <div className="hd">
        {/* 文本输入框,绑定inputRef到ref属性 */}
        <input type="text" ref={inputRef}/>
        <button onClick={handler}>确认</button>
      </div>
      
      {/* 子组件Child,将list状态作为prop传递给子组件 */}
      <Child list={list} />
    </div>
  )
}


子组件

  • 子组件通过参数解构 { list } 接收父组件传递的 prop
  • 使用 list 数组渲染 <ul> 列表
export default function Child({ list }) {
  return (
    <div className="bd">
        <ul>
          {
            list.map((item) => {
              return <li key={item}>{item}</li>
            })
          }
        </ul>
      </div>
  )
}

效果

image.png

二、子传父组件通讯

通过回调函数(callback)来实现的。回调函数是指 父组件传递给子组件的一个函数,子组件在适当的时机(如用户点击按钮、输入数据等)调用这个函数,从而将数据传递回父组件。

父组件

export default function () {
 const [list, setList] = useState(['html', 'css', 'js'])

  return (
    <div>
        <Child setList={setList} />
      <div className="bd">
        <ul>
          {
            list.map((item) => {
              return <li key={item}>{item}</li>
            })
          }
        </ul>
      </div>
    </div>
  )
}
  • setList 就是回调函数,它原本是 useState 提供的状态更新函数。
  • 父组件将它作为 propsetList={setList})传递给子组件 Child
  • 子组件可以调用 setList 来修改父组件的状态。

子组件

export default function Child({ setList }) {
const inputRef = useRef(null)
const handler = () => {
    setList((setList) => {
        return [...setList, inputRef.current.value]
    })
  }
  return (
     <div className="hd">
        <input type="text" ref={inputRef}/>
        <button onClick={handler}>确认</button>
      </div>
  )
}
  • setList 是从父组件接收的回调函数
  • 当用户点击按钮时,handler 被触发,子组件调用 setList 并传入新的数据(inputRef.current.value)。
  • 父组件的 list 状态被更新,并重新渲染 UI。

三、兄弟组件通讯

兄弟组件之间不能直接通信,通常需要通过共同的父组件作为中介来实现数据共享。

父组件

import Child1 from './Child1.jsx'
import Child2 from './Child2.jsx'
import { useState } from 'react'

export default function () {
  const [list, setList] = useState(['html', 'css', 'js'])
  return (
    <div>
      <Child1 setList={setList}/>
      <Child2 list={list}/>
    </div>
  )
}
  1. 维护共享状态 list(初始值为['html', 'css', 'js'])
  2. 将更新函数 setList 传递给 Child1(允许Child1修改list)
  3. 将当前 list 数据传递给 Child2(让Child2显示最新列表)

子组件1

import { useRef } from 'react'

export default function Child1({ setList }) {
  const inputRef = useRef(null)

  const handler = () => {
    setList((preList) => {
      return [...preList, inputRef.current.value]
    })
  }
  return (
    <div className="hd">
      <input type="text" ref={inputRef} />
      <button onClick={handler}>确认</button>
    </div>
  )
}
  1. 通过 useRef 获取输入框的DOM引用
  2. 定义 handler 函数处理按钮点击
  3. 调用父组件传来的 setList 函数更新列表
  4. 使用扩展运算符创建新数组(React状态不可变性原则)
  5. 将输入框的值添加到列表末尾
  6. 渲染输入框和按钮界面

子组件2

export default function Child2({ list }) {

  return (
    <div className="bd">
      <ul>
        {
          list.map((item) => {
            return <li key={item}>{item}</li>
          })
        }
      </ul>
    </div>
  )
}
  1. 接收父组件传来的 list 数据
  2. 使用 map 方法将数组转换为 <li> 元素列表
  3. 为每个列表项设置唯一的 key 属性(React列表渲染要求)
  4. 渲染出无序列表展示所有项目

四、跨组件通讯

对于不相邻的组件间通信,React Context API提供了一个强大的解决方案。它允许你在一个组件树中共享状态,而无需手动通过每一层传递props

跨组件通信能实现的效果

// 顶层组件
<ThemeProvider value="dark">
  <App>  // 第1层
    <Header>  // 第2层
      <UserMenu>  // 第3层
        <ThemeButton />  // 第4层 - 可以直接获取theme
      </UserMenu>
    </Header>
  </App>
</ThemeProvider>

首先创建一个Context:

import { createContext } from 'react';
const MsgContext = createContext();

然后,在顶层组件中使用Provider来包裹子组件,并提供要共享的数据:

<MsgContext.Provider value={msg}>
  <A />
</MsgContext.Provider>

最后,在任何子组件中都可以使用useContext钩子来访问这个上下文值:

const msg = useContext(MsgContext);

这种模式特别适用于状态管理,避免了繁琐的props钻取(prop drilling),使得代码更加简洁和可维护。

实例:

import { useState, createContext, useContext } from 'react';
// 1. 创建Context
const MsgContext = createContext();

function B() {
  // 3. 在子组件中使用useContext获取值
  const msg = useContext(MsgContext);
  console.log('B组件接收到的消息:', msg);
  
  return <div>B组件 - 接收到的消息: {msg}</div>;
}

function A() {
  return (
    <div>
      A组件
      <B />
    </div>
  );
}

export default function Index() {
  const [msg, setMsg] = useState('你好,这是一条跨层级消息');
  
  // 2. 使用Provider提供数据
  return (
    <div>
      <h1>跨层级通信示例</h1>
      <button onClick={() => setMsg('消息已更新: ' + new Date().toLocaleTimeString())}>
        更新消息
      </button>
      
      <MsgContext.Provider value={msg}>
        <A />
      </MsgContext.Provider>
    </div>
  );
}

五、状态管理库

当我们讨论简单的父子、兄弟或跨层级组件间的通信时,通过props传递数据或使用React的Context API通常已经足够。然而,在实际应用中,你可能会遇到组件结构非常复杂的情况,其中需要共享的状态跨越了多个层级或分布在不同的分支中。在这种情况下,手动通过props逐层传递数据会变得极其繁琐且难以维护。这时,采用一个全局状态管理解决方案就显得尤为重要。

由于这部分内容相对独立且较为深入,我们将在下一篇文章中详细介绍如何使用全局状态管理来实现复杂组件间的通信。

image.png