我将详细讲解 React 中各种组件通信方式,从基础到高级,并附上实际示例。
一、父子组件通信
1. Props 向下传递(父 → 子)
最基本的方式,父组件通过 props 向子组件传递数据。
jsx
// 父组件
import React from 'react';
import ChildComponent from './ChildComponent';
function ParentComponent() {
const message = "Hello from Parent";
const user = { name: "John", age: 30 };
return (
<div>
<h1>父组件</h1>
<ChildComponent
message={message}
user={user}
count={5}
/>
</div>
);
}
// 子组件
function ChildComponent(props) {
return (
<div>
<h2>子组件</h2>
<p>消息: {props.message}</p>
<p>用户名: {props.user.name}</p>
<p>计数: {props.count}</p>
</div>
);
}
// 或者使用解构
function ChildComponent({ message, user, count }) {
return (
<div>
<h2>子组件</h2>
<p>消息: {message}</p>
<p>用户名: {user.name}</p>
<p>计数: {count}</p>
</div>
);
}
2. 回调函数向上传递(子 → 父)
子组件通过调用父组件传递的回调函数,向父组件传递数据。
jsx
// 父组件
function ParentComponent() {
const [count, setCount] = useState(0);
// 处理子组件的数据
const handleChildData = (dataFromChild) => {
console.log("从子组件接收的数据:", dataFromChild);
setCount(dataFromChild);
};
const handleMessage = (message) => {
alert(`子组件说: ${message}`);
};
return (
<div>
<h1>父组件 - 计数: {count}</h1>
<ChildComponent
onDataSend={handleChildData}
onMessageSend={handleMessage}
/>
</div>
);
}
// 子组件
function ChildComponent({ onDataSend, onMessageSend }) {
const [inputValue, setInputValue] = useState('');
const handleClick = () => {
// 调用父组件传递的函数
onDataSend(10);
};
const handleSendMessage = () => {
onMessageSend(inputValue);
setInputValue('');
};
return (
<div>
<h2>子组件</h2>
<button onClick={handleClick}>发送数据到父组件</button>
<div>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="输入消息"
/>
<button onClick={handleSendMessage}>发送消息</button>
</div>
</div>
);
}
二、兄弟组件通信
3. 状态提升(通过共同的父组件)
将共享状态提升到最近的共同父组件中。
jsx
// 父组件
function ParentComponent() {
const [sharedData, setSharedData] = useState('初始数据');
return (
<div>
<h1>父组件</h1>
<ChildA
data={sharedData}
onDataChange={setSharedData}
/>
<ChildB data={sharedData} />
</div>
);
}
// 子组件A
function ChildA({ data, onDataChange }) {
return (
<div style={{ border: '1px solid blue', padding: '10px', margin: '10px' }}>
<h3>子组件A</h3>
<p>当前数据: {data}</p>
<button onClick={() => onDataChange('A组件修改的数据')}>
修改数据
</button>
</div>
);
}
// 子组件B
function ChildB({ data }) {
return (
<div style={{ border: '1px solid green', padding: '10px', margin: '10px' }}>
<h3>子组件B</h3>
<p>从A组件接收的数据: {data}</p>
</div>
);
}
三、跨层级组件通信
4. Context API(官方解决方案)
适用于跨多层组件传递数据,避免"prop drilling"(属性层层传递)。
jsx
// 1. 创建Context
import React, { createContext, useState, useContext } from 'react';
// 创建Context对象
const UserContext = createContext();
const ThemeContext = createContext();
// 2. 创建Provider组件
function App() {
const [user, setUser] = useState({ name: '张三', age: 25 });
const [theme, setTheme] = useState('light');
const updateUser = (newUser) => {
setUser({ ...user, ...newUser });
};
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
// 提供多个Context
<UserContext.Provider value={{ user, updateUser }}>
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div style={{ padding: '20px' }}>
<h1>主应用</h1>
<ComponentA />
<ComponentC />
</div>
</ThemeContext.Provider>
</UserContext.Provider>
);
}
// 3. 中间层组件(不需要传递props)
function ComponentA() {
return (
<div style={{ border: '1px solid gray', padding: '10px', margin: '10px' }}>
<h2>组件A</h2>
<ComponentB />
</div>
);
}
// 4. 消费Context的组件
function ComponentB() {
// 使用useContext Hook消费Context
const { user, updateUser } = useContext(UserContext);
const { theme, toggleTheme } = useContext(ThemeContext);
const style = {
backgroundColor: theme === 'light' ? '#f5f5f5' : '#333',
color: theme === 'light' ? '#000' : '#fff',
padding: '10px',
margin: '10px'
};
return (
<div style={style}>
<h3>组件B(使用Context)</h3>
<p>用户名: {user.name}</p>
<p>年龄: {user.age}</p>
<button onClick={() => updateUser({ age: user.age + 1 })}>
增加年龄
</button>
<button onClick={toggleTheme}>
切换主题 (当前: {theme})
</button>
</div>
);
}
// 5. 类组件消费Context(使用Consumer)
class ComponentC extends React.Component {
render() {
return (
<UserContext.Consumer>
{({ user }) => (
<ThemeContext.Consumer>
{({ theme }) => (
<div style={{
backgroundColor: theme === 'light' ? '#e8f5e9' : '#1b5e20',
padding: '10px',
margin: '10px'
}}>
<h3>组件C(类组件使用Consumer)</h3>
<p>用户名: {user.name}</p>
</div>
)}
</ThemeContext.Consumer>
)}
</UserContext.Consumer>
);
}
}
四、高级通信方式
5. 使用Ref(父组件直接调用子组件方法)
jsx
// 子组件(使用forwardRef和useImperativeHandle)
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
// 子组件
const ChildComponent = forwardRef((props, ref) => {
const [count, setCount] = useState(0);
// 暴露给父组件的方法
useImperativeHandle(ref, () => ({
// 子组件的方法
increment: () => {
setCount(count + 1);
},
decrement: () => {
setCount(count - 1);
},
reset: () => {
setCount(0);
},
// 子组件的数据
getCount: () => {
return count;
}
}));
return (
<div style={{ border: '1px solid red', padding: '10px', margin: '10px' }}>
<h3>子组件</h3>
<p>内部计数: {count}</p>
<button onClick={() => setCount(count + 1)}>内部+1</button>
</div>
);
});
// 父组件
function ParentComponent() {
const childRef = useRef(null);
const handleIncrement = () => {
childRef.current?.increment();
};
const handleDecrement = () => {
childRef.current?.decrement();
};
const handleReset = () => {
childRef.current?.reset();
};
const getChildCount = () => {
const count = childRef.current?.getCount();
alert(`子组件的计数: ${count}`);
};
return (
<div>
<h1>父组件</h1>
<button onClick={handleIncrement}>调用子组件increment()</button>
<button onClick={handleDecrement}>调用子组件decrement()</button>
<button onClick={handleReset}>调用子组件reset()</button>
<button onClick={getChildCount}>获取子组件计数</button>
<ChildComponent ref={childRef} />
</div>
);
}
6. 事件总线/发布订阅模式
适用于非父子组件、任意组件间的通信。
jsx
// eventBus.js - 事件总线
class EventBus {
constructor() {
this.events = {};
}
// 订阅事件
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
// 发布事件
emit(eventName, data) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => {
callback(data);
});
}
}
// 取消订阅
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
cb => cb !== callback
);
}
}
}
// 创建全局事件总线实例
export const eventBus = new EventBus();
// Component1.jsx - 发布者组件
import React, { useEffect, useState } from 'react';
import { eventBus } from './eventBus';
function Component1() {
const [message, setMessage] = useState('');
const sendMessage = () => {
// 发布事件
eventBus.emit('messageEvent', {
text: message,
timestamp: new Date().toLocaleTimeString()
});
setMessage('');
};
return (
<div style={{ border: '1px solid blue', padding: '10px', margin: '10px' }}>
<h3>组件1 (发布者)</h3>
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="输入消息"
/>
<button onClick={sendMessage}>发送消息</button>
<button onClick={() => eventBus.emit('globalEvent', '全局事件触发')}>
触发全局事件
</button>
</div>
);
}
// Component2.jsx - 订阅者组件
function Component2() {
const [receivedMessages, setReceivedMessages] = useState([]);
useEffect(() => {
// 定义事件处理函数
const handleMessage = (data) => {
setReceivedMessages(prev => [...prev, data]);
};
const handleGlobalEvent = (data) => {
console.log('收到全局事件:', data);
};
// 订阅事件
eventBus.on('messageEvent', handleMessage);
eventBus.on('globalEvent', handleGlobalEvent);
// 组件卸载时取消订阅
return () => {
eventBus.off('messageEvent', handleMessage);
eventBus.off('globalEvent', handleGlobalEvent);
};
}, []);
return (
<div style={{ border: '1px solid green', padding: '10px', margin: '10px' }}>
<h3>组件2 (订阅者)</h3>
<h4>收到的消息:</h4>
<ul>
{receivedMessages.map((msg, index) => (
<li key={index}>
{msg.text} - {msg.timestamp}
</li>
))}
</ul>
</div>
);
}
// App.jsx
function App() {
return (
<div>
<h1>事件总线示例</h1>
<Component1 />
<Component2 />
<Component3 />
</div>
);
}
// Component3.jsx - 另一个订阅者
function Component3() {
const [lastMessage, setLastMessage] = useState('');
useEffect(() => {
const handleMessage = (data) => {
setLastMessage(`最新消息: ${data.text} (${data.timestamp})`);
};
eventBus.on('messageEvent', handleMessage);
return () => {
eventBus.off('messageEvent', handleMessage);
};
}, []);
return (
<div style={{ border: '1px solid orange', padding: '10px', margin: '10px' }}>
<h3>组件3 (另一个订阅者)</h3>
<p>{lastMessage}</p>
</div>
);
}
7. 状态管理库(Redux/MobX/Zustand)
当应用状态复杂时,推荐使用状态管理库。这里以Zustand为例(最简单):
jsx
// store.js - 创建store
import create from 'zustand';
const useStore = create((set, get) => ({
// 状态
user: null,
todos: [],
theme: 'light',
// Actions - 更新状态的方法
setUser: (user) => set({ user }),
addTodo: (todo) => set((state) => ({
todos: [...state.todos, todo]
})),
removeTodo: (id) => set((state) => ({
todos: state.todos.filter(todo => todo.id !== id)
})),
toggleTheme: () => set((state) => ({
theme: state.theme === 'light' ? 'dark' : 'light'
})),
// 计算属性
get completedTodos() {
return get().todos.filter(todo => todo.completed);
},
// 异步action
fetchUser: async (userId) => {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
set({ user });
}
}));
// ComponentA.jsx - 使用store
function ComponentA() {
// 选择需要的state和actions
const { user, theme, setUser, toggleTheme } = useStore();
return (
<div style={{
backgroundColor: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#000' : '#fff',
padding: '20px',
margin: '10px'
}}>
<h3>组件A</h3>
<p>当前用户: {user ? user.name : '未登录'}</p>
<p>当前主题: {theme}</p>
<button onClick={() => setUser({ name: '李四', id: 1 })}>
设置用户
</button>
<button onClick={toggleTheme}>
切换主题
</button>
</div>
);
}
// ComponentB.jsx - 使用store的另一部分
function ComponentB() {
const { todos, addTodo, removeTodo, completedTodos } = useStore();
const [newTodo, setNewTodo] = useState('');
const handleAddTodo = () => {
if (newTodo.trim()) {
addTodo({
id: Date.now(),
text: newTodo,
completed: false
});
setNewTodo('');
}
};
return (
<div style={{ border: '1px solid #ccc', padding: '20px', margin: '10px' }}>
<h3>组件B - Todo列表</h3>
<div>
<input
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="输入todo"
/>
<button onClick={handleAddTodo}>添加</button>
</div>
<h4>所有Todo ({todos.length})</h4>
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)} style={{ marginLeft: '10px' }}>
删除
</button>
</li>
))}
</ul>
<h4>已完成 ({completedTodos.length})</h4>
<ul>
{completedTodos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
);
}
// App.jsx
function App() {
return (
<div>
<h1>Zustand状态管理示例</h1>
<ComponentA />
<ComponentB />
</div>
);
}
五、通信方式对比与选择指南
| 通信方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Props传递 | 父子组件简单通信 | 简单直观,React核心机制 | 多层传递麻烦(prop drilling) |
| 回调函数 | 子向父传递数据 | 简单直接 | 多层传递复杂 |
| 状态提升 | 兄弟组件通信 | 逻辑集中,易于理解 | 状态提升可能使父组件臃肿 |
| Context API | 跨多层组件,主题/用户等全局数据 | 官方方案,避免prop drilling | 不适合频繁更新的数据 |
| Ref | 父组件需要调用子组件方法 | 直接访问子组件 | 破坏React数据流,谨慎使用 |
| 事件总线 | 任意组件间通信,解耦组件 | 完全解耦,灵活 | 难以调试,可能产生内存泄漏 |
| 状态管理库 | 复杂应用状态管理 | 专业状态管理,可预测性 | 增加复杂度,学习成本 |
六、最佳实践建议
- 简单场景优先使用Props和回调函数
- 主题/用户等全局数据使用Context
- 复杂业务状态使用Redux/Zustand等状态库
- 尽量避免使用事件总线,除非必要
- 谨慎使用Ref直接操作子组件
总结
React 组件通信有多种方式,选择哪种取决于你的具体需求:
- 简单父子通信 → Props + 回调函数
- 兄弟组件通信 → 状态提升
- 跨层级组件 → Context API
- 复杂应用状态 → 状态管理库(Redux/Zustand)
- 特殊场景 → Ref 或事件总线
记住:保持数据流单向、可预测是 React 的核心原则。选择合适的通信方式可以让你的应用更易于维护和调试。