在前端开发内容学习中,React 组件通信详解:父传子、子传父、兄弟通信与 Context是常见主题。很多人在阅读时会遇到概念分散、步骤不清和注意点难以归纳的问题。本文按照基础概念、操作流程和关键细节,对相关内容进行整理。
React 最核心的思想之一是:

组件化开发
一个页面通常由很多组件组成:
App
├── Header
├── Sidebar
├── Main
│ ├── UserInfo
│ └── ProductList
└── Footer
组件拆分后,就会出现一个问题:
组件之间如何传递数据?
这就是:
组件通信(Component Communication)
例如:
用户信息
↓
Header显示用户名
↓
Sidebar显示头像
↓
Main显示个人资料
这些组件都需要同一份数据。
所以:
组件之间必须能够共享数据
React 项目中最常见的通信方式有:
父传子
↓
子传父
↓
兄弟组件通信
↓
跨组件通信(Context)
学习顺序也基本如此。
在学习组件通信之前,我们需要先理解 React 中一个很重要的概念:
State(状态)
很多初学者会把 State 和普通变量混淆,其实它们最大的区别就在于:
State 会驱动界面更新
普通变量不会
例如:
const count = 0
修改:
count++
页面并不会重新渲染。
但是:
const [count, setCount] = useState(0)
调用:
setCount(count + 1)
React 会重新渲染组件,页面也会同步更新。
所以,可以这样理解:
State 本身就是数据
只不过它是:
会变化的数据
并且:
React 会监听它的变化
所以,更准确地说:
状态(State)
=
会影响界面渲染的数据
或者一句话记住:
改变 State
=
改变数据
+
通知 React 更新页面
也就是说:
State(状态)
本身就是数据
只不过它是:
"会变的数据"
改变状态
=
改变数据内容
+
触发界面更新
最简单也是最常见的通信方式。
场景:
父组件有数据
子组件需要使用
父组件:
通过属性传递数据
子组件:
通过 props 接收数据
父组件:
import Child from './Child'
function App() {
const name = 'Rose'
return (
<Child name={name} />
)
}
子组件:
function Child(props) {
return (
<h1>{props.name}</h1>
)
}
export default Child
输出:
Rose
props:
properties(属性)
可以理解为:
父组件传递给子组件的数据包
例如:
<Child
name="Rose"
age={18}
/>
接收:
function Child(props){
console.log(props)
}
结果:
{
name:'Rose',
age:18
}
React 规定:
props 只能读
不能改
错误写法:
props.name = 'Tom'
原因:
数据由父组件管理
子组件无权修改
遵循:
单向数据流
场景:
子组件产生数据
父组件需要知道
例如:
点击按钮
↓
把数据传给父组件
React 中:
函数也可以传递
所以:
父组件传函数
子组件调用函数
通过参数传值
父组件:
import Child from './Child'
function App(){
const getData = (data)=>{
console.log(data)
}
return (
<Child getData={getData}/>
)
}
子组件:
function Child(props){
const sendData = ()=>{
props.getData('Hello React')
}
return (
<button onClick={sendData}>
点击
</button>
)
}
输出:
Hello React
父组件
创建函数
↓
传给子组件
↓
子组件调用函数
↓
传递数据
↓
父组件接收
记忆口诀:
父传函数
子调函数
参数传值
当多个组件需要共享同一份数据时,就会涉及:
状态提升
很多人第一次学习时容易理解成:
把数据传给父组件
其实这并不准确。
状态提升真正提升的不是数据,而是:
状态(State)的所有权
可以理解为:
把状态
以及
修改状态的权限
交给共同的父组件统一管理。
例如:
Child1
拥有 count
后来发现:
Child2
也需要 count
这时候,就不应该让 Child1 保存这份状态,而应该把它放到它们共同的父组件中:
Parent
↓
Child1
Child2
此时:
Parent
保存 State
再:
Parent
↓
通过 props
↓
传给 Child1
同时:
Parent
↓
通过 props
↓
传给 Child2
如果 Child1 想修改数据,也不能直接修改,而是:
Child1
↓
调用 Parent 传下来的函数
↓
Parent 更新 State
↓
重新传给两个子组件
整个过程如下:
Child1
↓
通知 Parent
↓
Parent 更新 State
↓
重新渲染
↓
Child1
Child2
所以,可以一句话记住状态提升:
状态提升的本质就是:
把"状态(数据 + 修改权限)"
从某个子组件手里
交给它们共同的父组件统一管理。
这样做最大的好处是:
数据只有一份
不会出现多个组件维护同一份数据
保证数据始终一致。
场景:
Child1
↓
Child2
两个组件互相传值。
例如:
购物车组件
↓
商品列表组件
错误理解:
Child1
↓
Child2
React 不支持这样直接传递。
必须经过:
Child1
↓
Parent
↓
Child2
例如:
子组件1
传值给父组件
↓
父组件保存数据
↓
传给子组件2
流程:
Child1
↓
Parent
↓
Child2
这也是:
状态提升(Lifting State Up)
思想。
Child1
点击按钮
↓
通知Parent
↓
Parent更新状态
↓
传给Child2
React 遵循:
单向数据流(One-Way Data Flow)
也就是说:
数据只能:
父组件
↓
子组件
不能直接:
Child1
↓
Child2
所以,当多个组件需要共享同一份数据时,就应该:
把状态放到
它们最近的共同父组件
再由父组件统一:
保存状态
↓
修改状态
↓
传递状态
这就是:
状态提升(Lifting State Up)
也是 React 中最经典、最重要的设计思想之一。
当项目变大时:
Header
Sidebar
UserCenter
ProductList
Cart
...
如果一直:
父传子
子传父
会出现:
层层传递
代码混乱
所以需要:
统一数据中心
也就是:
仓库(Store)
后面会学习:
Redux
Zustand
MobX
本质都是:
集中管理状态
场景:
App
↓
A
↓
B
↓
C
↓
D
D 需要使用 App 的数据。
如果使用 props:
App
↓
A
↓
B
↓
C
↓
D
层层传递。
这就是:
Props Drilling
属性穿透
解决:
跨层级组件通信
例如:
祖先组件
↓
任意后代组件
直接共享数据。
创建上下文对象:
import { createContext }
const UserContext =
createContext()
父组件:
<UserContext.Provider
value={'Rose'}
>
<Child />
</UserContext.Provider>
作用:
向后代组件共享数据
后代组件:
import {
useContext
} from 'react'
function Child(){
const value =
useContext(UserContext)
return (
<h1>{value}</h1>
)
}
结果:
Rose
createContext
↓
Provider
↓
useContext
记忆:
创建
提供
消费
避免:
props层层传递
例如:
App
↓
A
↓
B
↓
C
↓
D
D 可以直接获取:
App中的数据
无需经过:
A
B
C
父组件绑定属性
子组件使用props接收
不能
props是只读的
父组件传函数
子组件调用函数
通过参数传值
状态提升
通过共同父组件
流程:
Child1
↓
Parent
↓
Child2
解决跨层级组件通信
避免props层层传递
组件通信
│
├── 父传子
│ ↓
│ props
│
├── 子传父
│ ↓
│ 函数回调
│
├── 兄弟通信
│ ↓
│ 状态提升
│
└── 跨组件通信
↓
createContext
↓
Provider
↓
useContext
父传子:
props
props只读
----------------
子传父:
父传函数
子调函数
参数传值
----------------
兄弟通信:
状态提升
Child1
↓
Parent
↓
Child2
----------------
跨组件通信:
createContext
↓
Provider
↓
useContext