React 组件间通信方式#
1. 概述#
在 React 应用中,组件间通信是构建复杂应用的核心概念之一。不同组件之间需要共享数据和传递信息,以实现协同工作。根据组件之间的关系(父子、兄弟、跨层级等),我们可以选择不同的通信方式。
本文将详细介绍 React 中常用的组件间通信方式,包括基础方式和高级方式,每种方式都将提供适用场景和代码示例。
2. State 基础#
State 是 React 组件内部的数据源,分为两种类型:
- 判断依据:用于控制组件的行为和渲染逻辑(如布尔值控制显示/隐藏)
- 渲染数据源:直接用于组件 UI 渲染的数据(如列表数据、文本内容)
State 的特点:
- 组件内部可修改(通过
setState或useStateHook) - 状态变化会触发组件重新渲染
- 只能在组件内部访问(除非显式传递)
3. 父 - 子组件通信#
3.1 Props 传递#
Props 是父子组件通信的最基本方式,父组件通过 props 将数据传递给子组件。
适用场景:
- 父组件向子组件传递静态数据或动态数据
- 父组件向子组件传递回调函数
代码示例:
// 父组件
import React from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const parentData = "来自父组件的数据";
const handleChildEvent = (data) => {
console.log("从子组件接收的事件:", data);
};
return (
<div>
<ChildComponent
data={parentData}
onChildEvent={handleChildEvent}
/>
</div>
);
};
// 子组件
import React from 'react';
const ChildComponent = ({ data, onChildEvent }) => {
return (
<div>
<p>父组件传递的数据: {data}</p>
<button onClick={() => onChildEvent("子组件事件数据")}>
触发子组件事件
</button>
</div>
);
};3.2 Children 属性#
children 是 React 组件的特殊属性,用于传递组件内容。
适用场景:
- 父组件向子组件传递任意 React 元素或组件
- 实现布局组件(如容器、卡片)
代码示例:
// 布局组件
import React from 'react';
const Layout = ({ children }) => {
return (
<div className="layout">
<header>Header</header>
<main>{children}</main>
<footer>Footer</footer>
</div>
);
};
// 使用
const App = () => {
return (
<Layout>
<h1>页面标题</h1>
<p>页面内容...</p>
</Layout>
);
};4. 子 - 父组件通信#
4.1 函数回调#
子组件通过调用父组件传递的回调函数,将数据传递给父组件。
适用场景:
- 子组件需要通知父组件状态变化
- 子组件触发的事件需要父组件处理
代码示例:
// 父组件
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const [count, setCount] = useState(0);
const handleIncrement = (value) => {
setCount(prevCount => prevCount + value);
};
return (
<div>
<p>Count: {count}</p>
<ChildComponent onIncrement={handleIncrement} />
</div>
);
};
// 子组件
import React from 'react';
const ChildComponent = ({ onIncrement }) => {
const handleClick = () => {
onIncrement(1);
};
return (
<button onClick={handleClick}>
增加 Count
</button>
);
};5. 兄弟组件通信#
5.1 状态提升#
兄弟组件无法直接通信,需要通过状态提升的方式,将共享状态保存到最近的共同父组件中。
适用场景:
- 两个或多个兄弟组件需要共享状态
- 一个组件的状态变化需要影响其他兄弟组件
代码示例:
// 父组件
import React, { useState } from 'react';
import BrotherComponentA from './BrotherComponentA';
import BrotherComponentB from './BrotherComponentB';
const ParentComponent = () => {
const [sharedState, setSharedState] = useState("初始值");
const updateSharedState = (newValue) => {
setSharedState(newValue);
};
return (
<div>
<BrotherComponentA
sharedState={sharedState}
onUpdateState={updateSharedState}
/>
<BrotherComponentB sharedState={sharedState} />
</div>
);
};
// 兄弟组件 A
import React from 'react';
const BrotherComponentA = ({ sharedState, onUpdateState }) => {
return (
<div>
<p>组件 A 读取共享状态: {sharedState}</p>
<button onClick={() => onUpdateState("由组件 A 更新的值")}>
更新共享状态
</button>
</div>
);
};
// 兄弟组件 B
import React from 'react';
const BrotherComponentB = ({ sharedState }) => {
return (
<div>
<p>组件 B 读取共享状态: {sharedState}</p>
</div>
);
};6. 跨层级组件通信#
6.1 Context API#
Context API 是 React 提供的用于跨层级组件通信的方案,允许数据在组件树中传递而无需逐层手动传递 props。
适用场景:
- 多个组件需要访问相同的数据(如主题、用户信息、语言设置)
- 避免 props 逐层传递(prop drilling)
- 跨越多层级的组件通信
代码示例:
// 创建 Context
import React, { createContext, useContext, useState, ReactNode } from 'react';
// 定义 Context 类型
interface ThemeContextType {
theme: string;
toggleTheme: () => void;
}
// 创建 Context
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
// Context Provider 组件
interface ThemeProviderProps {
children: ReactNode;
}
export const ThemeProvider = ({ children }: ThemeProviderProps) => {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === "light" ? "dark" : "light");
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
// 自定义 Hook 方便使用 Context
export const useTheme = () => {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
};
// 根组件使用 Provider
import React from 'react';
import { ThemeProvider } from './ThemeContext';
import DeepChildComponent from './DeepChildComponent';
const App = () => {
return (
<ThemeProvider>
<div className="app">
<DeepChildComponent />
</div>
</ThemeProvider>
);
};
// 深层子组件使用 Context
import React from 'react';
import { useTheme } from './ThemeContext';
const DeepChildComponent = () => {
const { theme, toggleTheme } = useTheme();
return (
<div className={`component ${theme}`}>
<p>当前主题: {theme}</p>
<button onClick={toggleTheme}>
切换主题
</button>
</div>
);
};6.2 使用 React Hooks#
除了 Context API,还可以使用 React Hooks 实现更灵活的组件间通信。
6.2.1 useReducer + Context#
结合 useReducer 和 Context API,可以实现更复杂的状态管理和跨组件通信。
适用场景:
- 状态逻辑复杂,包含多个子值
- 下一个状态依赖于之前的状态
- 需要统一管理状态更新逻辑
代码示例:
import React, { createContext, useContext, useReducer, ReactNode } from 'react';
// 定义 Action 类型
type Action =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'SET'; payload: number };
// 定义 State 类型
interface CounterState {
count: number;
}
// 初始状态
const initialState: CounterState = {
count: 0
};
// Reducer 函数
const counterReducer = (state: CounterState, action: Action): CounterState => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'SET':
return { count: action.payload };
default:
return state;
}
};
// 创建 Context
interface CounterContextType {
state: CounterState;
dispatch: React.Dispatch<Action>;
}
const CounterContext = createContext<CounterContextType | undefined>(undefined);
// Provider 组件
interface CounterProviderProps {
children: ReactNode;
}
export const CounterProvider = ({ children }: CounterProviderProps) => {
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<CounterContext.Provider value={{ state, dispatch }}>
{children}
</CounterContext.Provider>
);
};
// 自定义 Hook
export const useCounter = () => {
const context = useContext(CounterContext);
if (context === undefined) {
throw new Error("useCounter must be used within a CounterProvider");
}
return context;
};
// 使用示例
const CounterDisplay = () => {
const { state } = useCounter();
return <p>Count: {state.count}</p>;
};
const CounterControls = () => {
const { dispatch } = useCounter();
return (
<div>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
</div>
);
};7. 全局状态管理#
7.1 Redux#
Redux 是一个用于 JavaScript 应用的可预测状态容器,适用于大型应用的全局状态管理。
核心概念:
- Store:存储应用的全局状态
- Action:描述状态变化的对象
- Reducer:根据 Action 更新状态的纯函数
- Dispatch:发送 Action 的函数
- Subscribe:监听状态变化的函数
适用场景:
- 大型应用,组件层级复杂
- 需要统一管理多个组件共享的状态
- 需要记录状态变化历史(用于调试或撤销/重做功能)
代码示例:
// 安装依赖:npm install redux react-redux @reduxjs/toolkit
// store.ts
import { configureStore, createSlice } from '@reduxjs/toolkit';
// 创建 Slice
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
// 导出 Action Creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 创建 Store
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
// 导出类型
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// 根组件
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store';
import CounterComponent from './CounterComponent';
const App = () => {
return (
<Provider store={store}>
<CounterComponent />
</Provider>
);
};
// 组件中使用
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './store';
import type { RootState } from './store';
const CounterComponent = () => {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
};7.2 MobX#
MobX 是另一个流行的状态管理库,采用响应式编程思想,使用起来更加简洁。
核心概念:
- Observable:可观察的状态
- Computed:派生状态,根据其他状态计算得出
- Action:修改状态的函数
- Reaction:响应状态变化的副作用
适用场景:
- 希望使用更简洁 API 的状态管理
- 响应式编程风格
- 中等规模应用
7.3 Recoil#
Recoil 是 Facebook 开发的状态管理库,专为 React 设计,提供了更接近 React 原生状态管理的体验。
核心概念:
- Atom:可共享的状态单元
- Selector:派生状态,根据 Atom 计算得出
- RecoilRoot:提供 Recoil 状态管理的上下文
适用场景:
- 希望使用 React 风格的状态管理
- 需要细粒度的状态控制
- 支持并发模式
8. 事件总线(Event Bus)#
事件总线是一种基于发布-订阅模式的通信方式,允许组件之间通过事件进行通信,无需直接依赖。
适用场景:
- 组件之间没有直接的层级关系
- 临时的、松散耦合的组件通信
- 跨多个组件的事件通知
代码示例:
// 创建事件总线
class EventBus {
private events: Record<string, Function[]> = {};
// 订阅事件
on(eventName: string, callback: Function) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
// 取消订阅
off(eventName: string, callback: Function) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
(cb) => cb !== callback
);
}
}
// 发布事件
emit(eventName: string, ...args: any[]) {
if (this.events[eventName]) {
this.events[eventName].forEach((callback) => {
callback(...args);
});
}
}
}
// 导出单例实例
export const eventBus = new EventBus();
// 组件 A 发布事件
import React from 'react';
import { eventBus } from './EventBus';
const ComponentA = () => {
const handleClick = () => {
eventBus.emit('customEvent', '事件数据');
};
return (
<button onClick={handleClick}>
触发事件
</button>
);
};
// 组件 B 订阅事件
import React, { useEffect, useState } from 'react';
import { eventBus } from './EventBus';
const ComponentB = () => {
const [eventData, setEventData] = useState<string>('');
useEffect(() => {
// 订阅事件
const handleCustomEvent = (data: string) => {
setEventData(data);
};
eventBus.on('customEvent', handleCustomEvent);
// 清理函数
return () => {
eventBus.off('customEvent', handleCustomEvent);
};
}, []);
return (
<div>
<p>接收的事件数据: {eventData}</p>
</div>
);
};9. Portals#
Portals 允许将组件渲染到 DOM 树中的任何位置,常用于模态框、对话框等组件。
适用场景:
- 模态框、对话框、通知等需要突破父组件层级的组件
- 需要在整个应用层面显示的组件
代码示例:
import React from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ isVisible, onClose, children }) => {
if (!isVisible) return null;
return ReactDOM.createPortal(
<div className="modal-overlay">
<div className="modal-content">
<button className="modal-close" onClick={onClose}>
×
</button>
{children}
</div>
</div>,
document.body // 渲染到 body 元素中
);
};
// 使用示例
import React, { useState } from 'react';
import Modal from './Modal';
const App = () => {
const [isModalVisible, setIsModalVisible] = useState(false);
return (
<div>
<button onClick={() => setIsModalVisible(true)}>
打开模态框
</button>
<Modal
isVisible={isModalVisible}
onClose={() => setIsModalVisible(false)}
>
<h2>模态框标题</h2>
<p>模态框内容...</p>
</Modal>
</div>
);
};10. 自定义 Hook#
自定义 Hook 允许将组件逻辑提取到可复用的函数中,也可以用于组件间通信。
适用场景:
- 复用组件逻辑
- 封装复杂的状态管理
- 组件间共享逻辑但不共享状态
代码示例:
// 自定义 Hook 用于管理表单状态
import { useState, ChangeEvent } from 'react';
interface FormValues {
[key: string]: string;
}
export const useForm = (initialValues: FormValues) => {
const [values, setValues] = useState(initialValues);
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setValues(prev => ({
...prev,
[name]: value
}));
};
const resetForm = () => {
setValues(initialValues);
};
return { values, handleChange, resetForm };
};
// 使用自定义 Hook
import React from 'react';
import { useForm } from './useForm';
const FormComponent = () => {
const { values, handleChange, resetForm } = useForm({
username: '',
email: ''
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log('表单数据:', values);
resetForm();
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="username"
value={values.username}
onChange={handleChange}
placeholder="用户名"
/>
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
placeholder="邮箱"
/>
<button type="submit">提交</button>
</form>
);
};11. 通信方式选择指南#
| 通信方式 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| Props 传递 | 父子组件 | 简单直接,React 原生支持 | 不适用于跨层级组件 |
| 函数回调 | 子父组件 | 实现子组件向父组件传递数据 | 需要显式传递回调函数 |
| 状态提升 | 兄弟组件 | 共享状态管理简单 | 状态逻辑集中在父组件,可能导致父组件臃肿 |
| Context API | 跨层级组件 | 避免 prop drilling | 不适用于复杂状态管理 |
| Redux | 全局状态 | 强大的状态管理,支持中间件 | 学习曲线陡峭,样板代码多 |
| MobX | 全局状态 | API 简洁,响应式编程 | 可能导致过度渲染 |
| Recoil | 全局状态 | React 原生风格,支持并发模式 | 相对较新,生态不如 Redux 成熟 |
| 事件总线 | 任意组件 | 松散耦合,使用灵活 | 可能导致难以追踪的数据流 |
| Portals | 特殊 UI 组件 | 突破 DOM 层级限制 | 主要用于 UI 渲染,不用于数据通信 |
| 自定义 Hook | 逻辑复用 | 复用组件逻辑,简化代码 | 不直接用于组件间数据共享 |
12. 最佳实践#
- 优先使用简单方式:从简单的通信方式开始,如 props 和回调,只有在需要时才使用更复杂的方式
- 避免过度设计:不要过早引入状态管理库,根据应用规模选择合适的方案
- 保持组件职责单一:每个组件只负责自己的状态和逻辑
- 合理使用 Context:Context 适合共享全局配置(如主题、语言),不适合频繁变化的状态
- 使用 TypeScript:TypeScript 可以提供类型安全,减少通信错误
- 测试组件通信:编写测试用例确保组件间通信正常工作
- 考虑性能:对于频繁变化的状态,使用 memo、useMemo 等优化手段避免不必要的渲染
13. 总结#
React 提供了多种组件间通信方式,每种方式都有其适用场景和优缺点。选择合适的通信方式取决于组件之间的关系、应用规模和性能要求。
- 对于简单的父子组件通信,使用 props 和回调函数
- 对于跨层级组件通信,使用 Context API
- 对于大型应用的全局状态管理,使用 Redux、MobX 或 Recoil
- 对于松散耦合的组件通信,使用事件总线
- 对于逻辑复用,使用自定义 Hook
理解和掌握这些通信方式,将有助于你构建结构清晰、性能优良的 React 应用。