# React组件通信:从父子传功到祖师爷的意念传音

100 阅读9分钟

一、门派通信概览

在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); // 依然是"杨铭"!

屏幕截图 2025-12-29 165811.png

教学要点:

  1. 只读性:子组件接收的props是父组件传入的副本
  2. 单向数据流:数据只能从父→子,确保数据流向清晰可预测
  3. 不可变性:这是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>;
}

屏幕录制 2025-12-29 182009.gif

  • 点击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>
  );
}

屏幕录制 2025-12-29 182533.gif

  • 父组件把自己的函数 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

屏幕录制 2025-12-29 183928.gif

四、师兄弟切磋:兄弟组件通信

当两个同级组件需要交流时,需要将状态提升到共同的父组件:

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>;
}
  • 图例

屏幕截图 2025-12-30 121753.png


  • 点击大哥组件 → 事件上报父组件 → 父组件更新状态 → 状态同步给二弟组件 → 二弟组件立即响应更新

屏幕录制 2025-12-29 225312.gif

详细通信流程:

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 核心概念

  1. createContext():创建传功通道
  2. Provider:祖师爷开启传功通道
  3. 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();  // 错误!
}

image.png

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任意祖先→后代

七、实战要点提醒

  1. Props只读是铁律:子组件永远不要尝试修改props
  2. 回调函数命名:使用on前缀表示事件,如onClickonChange
  3. Context使用原则:不要滥用!只用于真正需要跨层级传递的数据
  4. 状态提升时机:当两个组件需要共享状态时,考虑提到共同父组件

记住:好的React组件通信就像和谐的师门关系,职责清晰,数据流向明确,这样才能写出易维护、高性能的React应用!

  • 望学习愉快!!!