React 里组件越拆越多,数据该怎么在它们之间流动,是新手最容易懵的地方。很多初学者一上来就写满整个页面,等到要拆组件、传数据时彻底卡住。组件通信其实就四种常用方式:父传子用 props、子传父用回调、兄弟之间做状态提升、跨层级用 Context。本文给每种方式配一个最小可运行示例,并说明它什么时候该上。下面每种方式都从“它解决什么”讲起,对照着抄就能用。你照着示例改两遍,就能根据场景选对写法,不再东抄一段西抄一段。

一、父传子:用 props 把数据“递下去”
把组件想象成工厂的工序,父组件是上一道工序,子组件是下一道。父组件想把自己手里的数据交给子组件,就通过“属性”传过去,子组件在参数里接收,这个参数在 React 里叫 props。
function Child(props) {
return <p>你好,{props.name}</p>;
}
function Parent() {
const w3cschoolName = '编程狮';
return <Child name={w3cschoolName} />;
}
上面 Parent 把 w3cschoolName 通过 name 属性传给 Child,Child 用 props.name 取到并显示。注意 props 在子组件里是只读的,子组件不能直接改它,只能“用”,不能“改”——这是 React 的单向数据流设计,也是它好维护的原因。
当你要系统理解组件与 JSX 的写法,可以先过一遍 React 教程,把函数组件、props 和事件这套主线理顺,后面几种通信方式都是在这之上做变化。
💡 小提示:props 名字随便起,但尽量见名知意,别用
a、b这种含糊名字,组件一多你就分不清谁是谁了。
二、子传父:用回调函数“传回来”
父传子解决“往下给”,那子组件想告诉父组件一件事(比如按钮被点了、输入框变了),怎么办?React 不允许子组件直接改父组件的状态,正确做法和现实里“你按门铃通知屋里人”一样:父组件先把一个回调函数通过 props 传下去,子组件在合适的时机调用它,把数据带回来。
import { useState } from 'react';
function Child({ onGreet }) {
return <button onClick={() => onGreet('编程狮')}>点击打招呼</button>;
}
function Parent() {
const [msg, setMsg] = useState('');
return (
<div>
<Child onGreet={(value) => setMsg(value)} />
<p>收到:{msg}</p>
</div>
);
}
这里 Parent 把 onGreet 这个函数交给 Child,Child 点击时调用 onGreet('编程狮'),父组件的 setMsg 就被触发,状态更新、视图刷新。核心就一句:子传父,不是子改父,而是子“叫父自己改”。
组件里的状态与函数写法,底层都依赖 JavaScript 教程 里的箭头函数与解构赋值基础,把这两块吃透,回调这种写法会变得非常自然。
三、兄弟组件:把状态“提升”到共同父级
如果两个兄弟组件都要用同一份数据,谁也不该单独藏着——正确的做法是把这份数据提到它们共同的父组件里,再由父组件分别用 props 分发给两个孩子。这叫“状态提升”。
import { useState } from 'react';
function Input({ value, onChange }) {
return <input value={value} onChange={(e) => onChange(e.target.value)} />;
}
function Show({ value }) {
return <p>你输入了:{value}</p>;
}
function Parent() {
const [text, setText] = useState('');
return (
<>
<Input value={text} onChange={setText} />
<Show value={text} />
</>
);
}
Input 负责改、Show 负责显示,但它们俩本身都不存数据,真正的数据 text 在 Parent 手里。任何一方的变化都回流到父组件,再由父组件统一下发,兄弟之间就通过“共同的父亲”完成了通信。
⚠️ 注意:别试图让两个兄弟组件互相直接调用方法,React 没有这种官方通道。一律提升到父级,结构才清晰、才好调试。
四、跨层级:用 Context 避开“透传地狱”
前三种都靠 props 一层层传。可如果组件嵌套了五六层,最底下的组件要用最顶层的数据,就得每层都写一遍 props,又臭又长,这叫“透传地狱”。Context 就是为此而生的:它在组件树里开一条“专用通道”,顶层放数据,任何深层子孙直接取,中间层完全不掺和。
import { createContext, useContext } from 'react';
const UserContext = createContext();
function App() {
return (
<UserContext.Provider value="编程狮">
<DeepChild />
</UserContext.Provider>
);
}
function DeepChild() {
const name = useContext(UserContext);
return <p>深层组件拿到:{name}</p>;
}
App 用 Provider 把 value 放进去,DeepChild 哪怕隔了十层,也能用 useContext 直接拿到,中间组件一个 props 都不用写。记不住 useContext、useState 这些 hooks 的写法时,JavaScript 速查手册 能按名快速查到用法,写代码时旁边开一份很省心。
💡 小提示:Context 适合“全局、变化不频繁”的数据,比如主题、登录用户。如果数据更新很频繁,别全塞进一个 Context,否则一变全树重渲染,性能会吃亏。
五、四种方式怎么选:一张对照表
讲完四种,关键还是“什么时候用哪个”。记住一个原则:能就近就别绕远。父子相邻用 props 最直接;子要回传用回调;兄弟共用就提升状态;跨很多层才上 Context。下面这张表帮你快速对号入座:
| 场景 | 推荐方式 | 一句话理由 |
|---|---|---|
| 父给子数据 | props | 最直接,单向下传 |
| 子通知父 | 回调函数 | 子不擅改父,只负责呼叫 |
| 兄弟共用数据 | 状态提升 | 提到共同父级统一分发 |
| 跨多层取数据 | Context | 避免一层层透传 |
实际项目里往往是组合使用:父传子用 props,子传父用回调,多个兄弟共享就提升,全局配置再交给 Context。没有“最好”的方式,只有“最合适当前距离”的方式。
举一个真实组合:顶部导航要显示当前用户名,这是跨层数据,用 Context 存 user 最省事;导航里的搜索框输入要实时过滤下方列表,这是父子关系,用 props 加回调即可;列表和分页是两个兄弟,共享 page 状态,就提升到它们的共同父级。你看,四种方式在同一页面里各司其职,并不冲突。先想清楚“这段数据要在哪些组件之间流动”,再选最短的那条路,通信就不再是难题。

总结
React 组件通信的四种方式,本质都是在回答“数据往哪走”:父传子走 props,子传父走回调,兄弟共享走状态提升,跨层级走 Context。单向数据流是底色——数据只能从父往子流,子想影响父,就通过回调让父自己改。
你该带走的要点:第一,props 只读,子组件别试图直接改;第二,状态尽量放在“用得到它的最低共同父级”;第三,Context 留给真正跨层的全局数据。把这些记熟,组件之间的数据流向就不会再乱。下一步建议顺着 React 教程把 hooks 与组合模式练扎实,基础就真正稳了。
延伸学习
想把这块知识系统补齐,可以按这个顺序来:
- 先跟着 AI 设计网页实战课程 用 React 组件搭一个真实页面,实战课程把通信写法融入项目,边做边理解最牢;
- 想看更多“翻车现场”,读 React 渲染黑洞笔记,它把无限渲染黑洞、远程木马、HOC 套娃等反例都拆明白了;
- 在框架之间摇摆时,参考 前端三大框架对比,这篇讲清 React、Angular、Vue.js 的取舍,帮你少走选型弯路。
常见问题
Q:props 和 state 到底有什么区别?
A:props 是父组件传下来的“只读快递”,子组件只能看不能用改;state 是组件自己管的“可变内存”,用 useState 声明、自己改。一个对外只读,一个对内可变。
Q:子组件能直接改父组件的 state 吗?
A:不能直接改。正确姿势是父组件传一个回调函数下去,子组件调用它,由父组件在自己的函数里改 state,这叫“子传父用回调”。
Q:状态提升会不会让父组件变得很臃肿?
A:会有一点,但这是 React 推荐的清晰结构。如果父组件因此过大,可以把相关逻辑抽成自定义 hook,或用 Context 分担,别为了“瘦父组件”而破坏单向数据流。
Q:Context 是不是可以替代所有 props 透传?
A:不建议。Context 适合变化不频繁的全局数据,用太多会让组件耦合变强、重渲染范围变大。就近能传 props 的,就别上 Context。

免费 AI IDE



