在 React 开发中,组件是构成页面的基本单元,就像一个个独立的小个体。不同组件之间要协同工作,必然需要传递数据、互通消息 ------ 这就是组件通信。不同关系的组件(父子、兄弟、跨层级),通信方式各有讲究,我们今天就把这些 "悄悄话" 技巧讲明白。
一、父传子:长辈的 "零花钱"
核心逻辑 :这是 React 中最基础、最高频的通信方式,核心工具是 props。父组件作为数据的拥有者,像长辈给孩子零花钱一样,主动将数据通过 props 传递给子组件;子组件只能被动接收和使用,不能直接修改(React 单向数据流的核心体现)。
适用场景:所有父组件向直接嵌套的子组件传递数据的场景,比如传递展示文本、配置项、静态数据等。
父组件发钱现场
jsx
// src/demo1/Parent.jsx
import Child from "./Child"
export default function Parent() {
const state = {
name: '小橘' // 准备给孩子的"零花钱"------要传递的核心数据
}
return (
<div>
<h2>父组件</h2>
{/* 把 state.name 包装成 msg 属性,通过 props 传递给 Child 组件 */}
{/* props 的属性名(msg)可自定义,只要子组件对应接收即可 */}
<Child msg={state.name}/> {/* 递钱ing */}
</div>
)
}
子组件收到钱的反应
jsx
// src/demo1/Child.jsx
export default function Child(props) {
// 子组件的参数 props 是一个对象,包含了父组件传递的所有属性
console.log("收到钱了:", props); // 控制台打印:{msg: '小橘'},直观查看接收的数据
// 直接通过 props.属性名 使用父组件传递的数据
return <h3>子组件 -- 收到父组件消息:{props.msg}</h3>
}
关键说明:
-
props是只读的(immutable),子组件若想修改父组件的数据,不能直接改props.msg,必须通过父组件提供的方法(后续子传父会讲); -
除了基本类型数据,
props还能传递函数、数组、对象甚至 JSX 元素,灵活性极高。
二、子传父:孩子向家里 "报账"
核心逻辑 :子组件没有直接修改父组件数据的权限,想传递自己的数据给父组件,需要借助 "回调函数"------ 父组件提前定义好接收数据的函数(相当于给孩子的 "报销单"),通过 props 传递给子组件;子组件在需要传递数据时(比如点击按钮、表单输入完成),调用这个回调函数并传入数据,父组件就能接收并处理。
适用场景:子组件触发交互后需同步数据到父组件,比如表单提交、按钮点击后的状态更新、子组件内部数据变化反馈等。
父组件发报销单
jsx
// src/demo2/Parent.jsx
import Child from "./Child"
import { useState } from 'react'
export default function Parent() {
// 父组件用 useState 维护自己的状态(初始值为 1)
let [count, setCount] = useState(1)
// 定义回调函数:专门接收子组件传来的数据并处理
const getNum = (n) => {
// n 就是子组件传递过来的参数(报销金额)
setCount(n); // 用子组件的数据更新父组件的状态
}
return (
<div>
<h2>父组件二 -- 当前金额:{count}</h2>
{/* 把回调函数 getNum 通过 props 传给子组件,相当于递上报销单 */}
<Child getNum={getNum}/>
</div>
)
}
子组件填单报销
jsx
// src/demo2/Child.jsx
export default function Child(props) {
const state = { num: 100 } // 子组件自己的内部数据------要报销的金额
// 定义发送函数:触发数据传递的逻辑
function send() {
// 调用父组件通过 props 传递的回调函数 getNum
// 把要传递的数据(state.num)作为参数传入,相当于提交报销单
props.getNum(state.num)
}
return (
<div>
<h3>子组件二</h3>
{/* 点击按钮触发 send 函数,完成数据传递 */}
<button onClick={send}>提交报销</button>
</div>
)
}
关键说明:
-
回调函数的本质是 "父组件把自己的方法传给子组件",子组件通过调用方法间接影响父组件,符合 React 单向数据流;
-
传递的数据可以是任意类型,多个数据可封装成对象传入(比如
props.getNum({ num: 100, reason: '买文具' }))。
三、兄弟通信:借爸妈当 "传话筒"
核心逻辑:兄弟组件(同一父组件的直接子组件)之间没有直接的通信通道,必须借助它们的共同父组件作为 "传话筒"------ 这也是 React 官方推荐的 "状态提升" 方案。流程是:兄弟 A 先把数据传给父组件(子传父),父组件接收后更新自己的状态,再把状态传给兄弟 B(父传子),完成间接通信。
适用场景:平级组件需要共享数据或同步状态,比如一个开关组件和一个状态展示组件、输入框和搜索结果面板等。
爸妈当传话筒(父组件)
jsx
// src/demo3/Parent.jsx
import { useState } from "react"
import Child1 from "./Child1"
import Child2 from "./Child2"
export default function Parent() {
// 父组件用 state 存储兄弟组件要共享的消息(初始值为 undefined)
let [message, setMessage] = useState()
// 回调函数:接收哥哥(Child1)传递的消息并更新状态
const getMsg = (msg) => {
setMessage(msg); // 把哥哥的消息存到父组件的状态里
}
return (
<div>
<h2> 父组件三 </h2>
{/* 给哥哥(Child1)传回调函数,用于接收它的消息 */}
<Child1 getMsg={getMsg}/> {/* 听哥哥说 */}
{/* 把存储的消息通过 props 传给弟弟(Child2) */}
<Child2 message={message}/> {/* 告诉弟弟 */}
</div>
)
}
哥哥组件(发消息方)
jsx
// src/demo3/Child1.jsx
export default function Child1(props) {
// 哥哥要发送给弟弟的消息
const state = {
msg: '弟弟你好,我是哥哥!'
}
// 点击按钮触发发送:调用父组件的回调函数,把消息传给父组件
function send() {
props.getMsg(state.msg);
}
return (
<div>
<h3>子组件3.1(哥哥)</h3>
<button onClick={send}>给弟弟发消息</button>
</div>
)
}
弟弟组件(收消息方)
jsx
// src/demo3/Child2.jsx
export default function Child2(props) {
// 直接通过 props 接收父组件转发的、来自哥哥的消息
// 用 || '暂无消息' 处理初始无消息的情况,避免页面显示 undefined
return (
<div>
<h3>子组件3.2(弟弟)</h3>
<p>收到哥哥的消息:{props.message || '暂无消息'}</p>
</div>
)
}
关键说明:
-
核心是 "状态提升"------ 把兄弟组件的共享状态抽到父组件管理,让父组件成为数据的唯一来源,保证数据流清晰;
-
若兄弟组件层级较深(比如不是直接子组件),可结合 Context 或状态管理库,但简单场景下状态提升足够用。
四、跨级通信:家族 "微信群"
核心逻辑 :当组件层级很深(比如爷爷→爸爸→儿子→孙子),深层子组件想获取顶层组件的数据时,用 props 逐层传递会非常繁琐(俗称 "props 透传")。这时可以用 Context API 建立一个 "家族微信群":顶层组件作为 "群主" 提供数据,所有需要数据的组件(无论层级多深)都能直接 "看群消息",无需中间组件转发。
适用场景:多层嵌套组件共享全局数据,比如用户登录状态、主题设置、语言配置等。
建个家族群(顶层父组件)
jsx
// src/demo4/Parent.jsx
import Child1 from "./Child1"
import { createContext } from 'react'
// 1. 创建 Context 对象:相当于新建一个"家族微信群",可设置默认值(可选)
export const Context = createContext()
export default function Parent() {
// 要在群里共享的数据------所有"群成员"都能访问
const parentData = '父组件的数据'
return (
<div>
<h2> 父组件四 </h2>
{/* 2. 用 Context.Provider 包装子组件树:相当于指定"群成员"范围 */}
{/* value 属性是要共享的数据:相当于在群里发消息 */}
<Context.Provider value={parentData}>
<Child1/> {/* Child1 及它的子组件都能访问 Context 数据 */}
</Context.Provider>
</div>
)
}
中间组件(无需转发消息)
jsx
// src/demo4/Child1.jsx
import Child2 from "./Child2"
export default function Child1() {
// 中间组件不需要处理 Context 数据,也不用传递 props
// 直接渲染子组件即可,数据会"穿透"到深层组件
return (
<div>
<h3>子组件(中间层)</h3>
<Child2></Child2> {/* Child2 是深层子组件,能直接访问 Context */}
</div>
)
}
重孙看群消息(深层子组件)
jsx
// src/demo4/Child2.jsx
import { useContext } from 'react'
import { Context } from './Parent'
export default function Child2() {
// 3. 用 useContext 钩子:相当于"查看群消息",直接获取 Context 中的数据
const msg = useContext(Context)
return <h4>孙子组件 --- 收到跨级消息:{msg}</h4>
}
关键说明:
-
Context 不是 "全局状态管理",更适合 "局部跨层级共享",滥用会导致组件重渲染性能问题;
-
若需要修改 Context 中的数据,可在顶层组件定义修改方法并一起放入
value,深层组件调用方法即可(比如value={{ data: parentData, setData: setParentData }})。
总结:组件通信的 "核心原则"
React 组件通信的本质是 "数据的有序流动",不同场景对应不同方案,核心原则是 "简单优先、数据流清晰":
-
父传子:用
props直接传,简单直接; -
子传父:用回调函数,间接反馈;
-
兄弟通信:状态提升到父组件,借父组件中转;
-
跨级通信:用 Context API,避免 props 透传。
这些方案覆盖了大部分日常开发场景,若遇到大型应用、多组件共享复杂状态的情况,再考虑其他方法。记住:组件通信不需要追求 "高大上",能清晰、高效传递数据的方案就是好方案