在 React 应用开发中,组件通信是构建交互逻辑的基础。由于 React 遵循“单向数据流”原则,数据通常从父组件流向子组件,但实际场景中常需实现父子双向通信、兄弟组件同步、跨多层传递甚至全局状态共享。掌握不同层级下的通信策略,是编写可维护、高性能 React 应用的关键。

父子组件通信:props 与回调函数
这是最基础且最常用的通信方式。父组件通过 props 向子组件传递数据或方法,子组件通过调用父组件传入的回调函数实现反向通信。
// 父组件
function Parent() {
const [count, setCount] = useState(0);
const handleIncrement = () => setCount(c => c + 1);
return <Child count={count} onIncrement={handleIncrement} />;
}
// 子组件
function Child({ count, onIncrement }) {
return (
<div>
<p>Count: {count}</p>
<button onClick={onIncrement}>+1</button>
</div>
);
}
此模式清晰、可预测,适用于直接的父子关系。若子组件需要传递复杂数据,回调函数可接收参数,如 onUpdate(value)。
兄弟组件通信:通过共同父组件中转
兄弟组件之间无直接引用,需借助最近的公共父组件作为中介。父组件定义共享状态和更新函数,分别传递给两个子组件。
function SiblingA({ value, onChange }) {
return <input value={value} onChange={e => onChange(e.target.value)} />;
}
function SiblingB({ value }) {
return <p>Current value: {value}</p>;
}
function Parent() {
const [sharedValue, setSharedValue] = useState('');
return (
<>
<SiblingA value={sharedValue} onChange={setSharedValue} />
<SiblingB value={sharedValue} />
</>
);
}
虽然有效,但当兄弟组件层级较深或数量较多时,状态提升会导致中间组件“被迫透传 props”,形成“prop drilling”问题。
跨多层组件通信:Context API
为解决 prop drilling,React 提供 Context API,允许在组件树中跨任意层级传递数据,无需手动逐层传递 props。
const ThemeContext = createContext();
// 顶层提供者
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<DeeplyNestedComponent />
</ThemeContext.Provider>
);
}
// 任意深层子组件消费
function DeepChild() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Switch to {theme === 'light' ? 'dark' : 'light'}
</button>
);
}
Context 适合共享全局性、低频变化的数据(如主题、用户信息、语言配置)。但需注意:任何 Context 值变化都会导致所有消费该 Context 的组件重新渲染,因此应将 Context 拆分为多个细粒度上下文,或配合 useMemo 优化提供值。
全局状态管理:Redux / Zustand / Jotai
当应用规模扩大,状态逻辑复杂(如购物车、用户会话、表单缓存),Context 可能难以维护。此时可引入专用状态管理库:
- Redux:单 store、不可变状态、严格单向数据流,适合大型团队项目;
- Zustand:轻量、基于 hooks、无需 Provider,API 简洁;
- Jotai:原子化状态模型,支持异步与派生状态,理念接近 Recoil。
以 Zustand 为例:
// store.js
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
}));
// 任意组件使用
function Counter() {
const { count, inc } = useStore();
return <button onClick={inc}>{count}</button>;
}
这类方案将状态与组件解耦,便于测试、调试和复用。
非受控组件通信:useRef 与 DOM 操作
对于非受控组件(如原生 <input>、第三方 UI 库),可通过 useRef 获取 DOM 实例或组件实例,直接读取/操作其值。
function Form() {
const inputRef = useRef();
const handleSubmit = () => {
console.log(inputRef.current.value); // 直接读取输入值
};
return (
<>
<input ref={inputRef} />
<button onClick={handleSubmit}>Submit</button>
</>
);
}
此方式打破单向数据流,应谨慎使用,仅限于集成非 React 控制的外部系统。
事件总线(不推荐,仅作了解)
在极少数无法使用上述方案的场景(如微前端子应用间),可借助简易事件总线(如自定义 EventEmitter)进行通信。但这种方式破坏组件封装性,难以追踪数据流,应尽量避免。
总结:按场景选择合适方案
| 通信关系 | 推荐方案 |
|---|---|
| 父 → 子 | props |
| 子 → 父 | 回调函数(props 传递函数) |
| 兄弟组件 | 状态提升至公共父组件 |
| 跨多层(3+ 层) | Context API |
| 全局/复杂状态 | Zustand / Redux / Jotai |
| 非受控组件 | useRef + DOM 操作 |
核心原则:优先使用最简单、最局部的方案。只有当 prop drilling 严重或状态逻辑复杂到影响可维护性时,才升级到 Context 或状态管理库。合理分层,才能让 React 应用既灵活又健壮。