一、门派通信概览
在React世界中,组件间的通信就像武林门派中的师徒传承、兄弟切磋:
| 通信场景 | 武林比喻 | React实现 | 核心特点 | 适用场景 |
|---|---|---|---|---|
| 祖师爷 → 徒弟 | 跨层级传功 | Context API | 建立全局通道,无视中间层级 | 主题切换、用户认证、全局状态等需要跨越多层组件的数据传递 |
| 师傅 → 徒弟 | 正向传功 | Props传递 | 单向数据流,数据只读不可改 | 父组件向子组件传递数据、配置参数或展示内容 |
| 徒弟 → 师傅 | 反向请益 | 回调函数 | 子组件通知父组件,触发状态更新 | 表单提交、按钮点击、子组件状态变化时需要通知父组件 |
| 师兄弟之间 | 同级切磋 | 状态提升 | 状态提到共同父组件,通过Props共享 | 两个同级组件需要共享或同步数据,但又不想引入全局状态管理 |
通信关系图:
graph TD
A[祖师爷 App组件] -->|Context跨层级| D[第三代弟子]
B[师傅 父组件] -->|Props传递| C[徒弟 子组件]
C -->|回调函数| B
E[师兄弟A] -->|状态提升到| F[共同父组件]
F -->|Props传递| G[师兄弟B]
style A fill:#f96
style B fill:#69f
style C fill:#9cf
style D fill:#9f6
style E fill:#c9f
style F fill:#f9f
style G fill:#c9f
武林秘诀:
- Props传功:秘籍只能阅读,不可篡改(单向数据流)
- 回调请益:徒弟不懂就问,师傅答疑解惑(事件冒泡)
- 状态切磋:兄弟比武,请父辈做裁判(状态提升)
- Context传音:祖师爷千里传音,徒孙直接领悟(跨组件通信)
这样理解React组件通信,是不是更有武林气息了?接下来我们逐一详解每种"传功"方式!
二、师门铁律:Props不可篡改
核心原理:Object.freeze(props)
React将props对象深度冻结,徒弟只能阅读秘籍,不能修改原本:
// React内部机制简化示意
const props = { name: "杨铭", age: "18" };
Object.freeze(props); // 深度冻结!
// 任何修改尝试都将失败
props.name = "小狗";
console.log(props.name); // 依然是"杨铭"!
教学要点:
- 只读性:子组件接收的props是父组件传入的副本
- 单向数据流:数据只能从父→子,确保数据流向清晰可预测
- 不可变性:这是React性能优化的基石,便于快速比较前后差异
三、师徒传功:父子组件通信
1. 师傅传给徒弟(父→子)
function App() {
const [energy, setEnergy] = useState(100);
const changEnger = () => {
setEnergy(energy - 10); // 师傅传功,消耗内力
}
return (
<>
{/* 传功给徒弟:能量差值 */}
<Disciple Cenger={100 - energy} />
<p>当前师傅自身的能量:{energy}</p>
//点击button按钮,触发changEnger函数,改变了energy从而改变了Cenger
<button onClick={changEnger}>把能量传给徒弟</button>
</>
);
}
function Disciple({ Cenger }) {
// 徒弟只能接收,不能修改Cenger
return <p>徒弟当前能量:{Cenger}</p>;
}
- 点击button按钮,触发changEnger函数,改变了energy从而改变了Cenger
2. 徒弟请教师傅(子→父)
通过回调函数让徒弟"反向传功":
function App() {
const [energy, setEnergy] = useState(100);
// 师傅准备好接收徒弟传功的方法
const getEnergy = (discipleEnergy) => {
setEnergy(energy + discipleEnergy);
}
return (
<>
{/* 把接收功力的方法传给徒弟 */}
<Disciple SetEnergy={getEnergy} />
</>
);
}
function Disciple({ SetEnergy }) {
// 徒弟调用父组件方法,传回内力
const ReturnEnergy = () => {
SetEnergy(10); // 传回10点内力
}
return (
<button onClick={ReturnEnergy}>把能量传回给师傅</button>
);
}
- 父组件把自己的函数
changEnger()传给了子组件,子组件通过点击事件onClick{}调用这个ReturnEnergy()函数,ReturnEnergy()函数里面又包含了changEnger()函数,这样就可以反过来改变父组件的状态,从而实现子向父通信的效果
完整师徒传功示例:
import './App.css'
import{useState}from 'react'
function App() {
//响应式数据
const [energy,setEnergy] = useState(100);
const changEnger = ()=>{
console.log("传10点能量给我的好徒儿");
//每次运行这个函数,当前能量-10点
setEnergy(energy-10);
}
const getEnergy = (discipleEnergy)=>{
setEnergy(energy+discipleEnergy);
}
return(
<>
<Disciple
//子组件拥有的props
Cenger={100-energy}
SetEnergy={getEnergy}
/>
<p>当前师傅自身的能量:{energy}</p>
//点击按钮触发changEnger()函数
<button onClick={changEnger}>传10点能量给我的好徒儿</button>
</>
)
}
function Disciple({Cenger,SetEnergy}) {
const ReturnEnergy = ()=>{
console.log("传10点能量给我的好师傅")
SetEnergy(10)
}
return(
<>
<p>徒弟当前能量:{Cenger}</p>
<button onClick={ReturnEnergy}>把10点能量传回给我的好师傅</button>
</>
)}
export default App
四、师兄弟切磋:兄弟组件通信
当两个同级组件需要交流时,需要将状态提升到共同的父组件:
import { useState } from "react";
function App() {
const skill = ["右臂外侧格挡", "左臂内侧格挡", "双臂下压防御", "拍挡卸力", "提膝格挡", "小腿外侧格挡", "十字手上架", "后撤步闪避"];
const [curindex, setCurindex] = useState(0);
const [botherskill, setBotherskill] = useState('');
const [tother, setTother] = useState('');
const [x, setX] = useState(''); // 存储所有招式记录
// 大哥出招,二弟自动接招
const changeSkillOne = (displaySkill) => {
const newBotherSkill = skill[(curindex + 1) % 8];
setX(prev => prev + `大哥${displaySkill}---VS---二弟${newBotherSkill}\n`);
setTother(displaySkill);
setBotherskill(newBotherSkill);
setCurindex(curindex + 1);
}
return (
<div>
<BotherOne SkillOne={changeSkillOne} OneSkill={tother} />
<BotherTwo TwoSkill={botherskill} />
<p style={{ whiteSpace: 'pre-line' }}>总招式:{x}</p>
</div>
);
}
function BotherOne({ SkillOne, OneSkill }) {
const skill = ["左勾拳", "右勾拳", "上勾拳", "直拳", "侧踢", "低扫", "高扫", "回旋踢"];
const [curindex, setCurindex] = useState(0);
const Skill = () => {
SkillOne(skill[curindex]); // 告诉父组件大哥出什么招
setCurindex((curindex + 1) % 8);
}
return (
<>
<button onClick={Skill}>点击:大哥出招:</button>
<p>大哥出招:{OneSkill}</p>
</>
);
}
function BotherTwo({ TwoSkill }) {
return <p>二弟接招:{TwoSkill}</p>;
}
- 图例
- 点击大哥组件 → 事件上报父组件 → 父组件更新状态 → 状态同步给二弟组件 → 二弟组件立即响应更新
详细通信流程:
1. 数据流向(Props传递)
App → BotherOne: SkillOne(函数), OneSkill(字符串)
App → BotherTwo: TwoSkill(字符串)
2. 事件触发流程
用户点击按钮
↓
BotherOne.Skill()被调用
↓
调用SkillOne(skill[curindex]) → 向App传递大哥招式
↓
App.changeSkillOne()执行:
1. 计算二弟接招
2. 更新招式记录x
3. 更新tother和botherskill状态
↓
状态更新触发重新渲染:
- BotherOne收到新的OneSkill
- BotherTwo收到新的TwoSkill
3. 状态管理
- App组件:管理核心状态(当前索引、招式记录、双方招式显示)
- BotherOne组件:管理自己的招式索引(独立状态)
- BotherTwo组件:纯展示组件,无状态,展示的数据来自BotherOne组件回调给App组件,然后,App组件传给BotherTwo组件
4. 特点说明
- 这是一个单向数据流:事件从子组件触发,状态在父组件更新,再通过props传递给子组件
- 两个curindex状态:App和BotherOne各自维护,相互独立
- 招式记录:在App组件中累积所有对战记录
- 招式匹配:大哥出拳法,二弟自动接对应的格挡技
- 招式类别:这是React典型的"状态提升"模式,将共享状态放在最近的共同祖先组件中管理。
五、祖师爷隔代传功:跨层级通信
当需要从祖师爷直接传功给第三代弟子时,使用 Context API。这好比建立了一条"意念传功"通道,无需再让徒弟们一层层转达
Context API 核心概念
- createContext():创建传功通道
- Provider:祖师爷开启传功通道
- useContext():徒弟接收祖师爷功力
// 导入React核心API
import { useState, useContext, createContext } from "react";
// 1️⃣ 创建Context:建立全局的"传功通道"
// ⚠️ 注意:必须创建在组件外部,不能写在函数里面
const UseContext = createContext(); // 这条通道叫"UseContext"
function App(){
// 2️⃣ 定义要传递的状态数据
const [use, setUse] = useState(""); // use是状态,setUse是更新函数
// 点击按钮时的处理函数
const change = () => {
console.log("点击向第三代徒弟通信")
// 更新状态,添加新消息
setUse(use + "你好杨铭徒弟,我是你的祖师爷。\n")
}
return(
<>
{/* 3️⃣ Provider:开启传功通道,包裹需要接收数据的组件 */}
{/* value属性:传递数据和函数给所有子组件 */}
<UseContext.Provider value = {{use, setUse}}>
<Child/> {/* Child组件内部的所有组件都能接收到数据 */}
</UseContext.Provider>
{/* 按钮在Provider外面,但仍能操作状态 */}
<button onClick={change}>点击向第三代徒弟通信</button>
</>
)
}
// 中间组件:不直接使用Context,只是传递
function Child(){
return(
<>
<ChildOne/> {/* 这里是第三代组件 */}
</>
)
}
// 第三代组件:接收数据
function ChildOne(){
// 4️⃣ useContext:接收祖师爷传来的"功力"
// 从UseContext通道中取出use和setUse
const {use, setUse} = useContext(UseContext) // 解构出需要的数据
return(
<>
{/* whiteSpace: 'pre-line' 让换行符生效 */}
<p style={{ whiteSpace: 'pre-line' }}>祖师爷向我通信:{use}</p><br/>
</>
)
}
export default App;
📝 重要注意事项
1. Context创建位置
// ✅ 正确:全局变量,组件外部
const MyContext = createContext();
// ❌ 错误:组件内部,每次渲染都会创建新Context
function App() {
const MyContext = createContext(); // 错误!
}
2. Provider的value属性
// ✅ 正确:直接传递对象
value={{use, setUse}}
// ❌ 注意:每次渲染都会创建新对象,可能导致性能问题
// 如果对象复杂,考虑使用useMemo优化
3. 组件嵌套层级
- Provider下的所有层级组件都能使用useContext接收数据
- 不需要逐层传递props(这就是Context的最大优势)
- 中间组件也能选择性地使用或不使用Context
4. 常见错误
// 错误1:忘记解构
const context = useContext(UseContext);
console.log(context.use); // 正确访问方式
// 错误2:拼写错误
const {use, setUSe} = useContext(UseContext); // setUse写成setUSe
// 错误3:不在Provider内使用
// 如果组件不在Provider包裹内,useContext返回undefined或默认值
5. 代码命名优化以及建议
// 1. 给Context起更有意义的名字
const MessageContext = createContext(); // 比UseContext更好
// 2. 添加默认值(可选)
const MessageContext = createContext({
message: "",
setMessage: () => console.warn("组件不在Provider内")
});
// 3. 创建自定义Hook
const useMessage = () => {
const context = useContext(MessageContext);
if (!context) {
throw new Error("useMessage必须在Provider内使用");
}
return context;
};
6.Context原理深度解析
- createContext建通道,Provider开通道传数据,useContext接数据,三步实现跨组件通信。
// 伪代码理解Context工作流程:
const MyContext = createContext(defaultValue);
// Provider组件:
function Provider({ value, children }) {
// 在组件树中"埋下"数据
MyContext._currentValue = value;
return children; // 子组件都可以获取到这个value
}
// useContext钩子:
function useContext(context) {
// 直接从最近的上层Provider获取数据
return context._currentValue;
}
六、通信方式总结表
| 通信方式 | 武林比喻 | 适用场景 | React实现 | 数据流向 |
|---|---|---|---|---|
| Props | 师傅传秘籍 | 父子通信 | <Child data={value} /> | 父→子 |
| 回调函数 | 徒弟请教 | 子父通信 | <Child onEvent={handler} /> | 子→父 |
| 状态提升 | 兄弟切磋 | 兄弟通信 | 状态提到共同父组件 | 兄弟↔父↔兄弟 |
| Context | 祖师爷传功 | 跨层级 | createContext() + Provider | 任意祖先→后代 |
七、实战要点提醒
- Props只读是铁律:子组件永远不要尝试修改props
- 回调函数命名:使用
on前缀表示事件,如onClick、onChange - Context使用原则:不要滥用!只用于真正需要跨层级传递的数据
- 状态提升时机:当两个组件需要共享状态时,考虑提到共同父组件
记住:好的React组件通信就像和谐的师门关系,职责清晰,数据流向明确,这样才能写出易维护、高性能的React应用!
- 望学习愉快!!!