在 React跨层级或非父子组件间的数据传递非常常见,通过总线模式实现发布订阅的 Hook 可以有效解耦组件通信,以下是基于总线模式的实现方案及代码示例:
一、核心实现原理
总线模式通过一个中心化的 事件总线(Event Bus) 管理事件的订阅与发布。其核心包含:
- 事件注册表:存储事件名称与对应的回调函数列表
- 订阅机制:组件通过事件名称注册回调函数
- 发布机制:触发事件时执行所有相关回调
- 取消订阅:组件卸载时自动清理订阅
二、自定义 Hook 实现
import { useEffect, useCallback, useRef } from 'react';
// 创建事件总线实例
const createEventBus = () => {
const events = useRef({});
const subscribe = useCallback((eventName, callback) => {
events.current[eventName] = events.current[eventName] || [];
events.current[eventName].push(callback);
// 返回取消订阅函数
return () => {
events.current[eventName] = events.current[eventName].filter(cb => cb !== callback);
};
}, []);
const publish = useCallback((eventName, data) => {
if (!events.current[eventName]) return;
events.current[eventName].forEach(cb => cb(data));
}, []);
return { subscribe, publish };
};
// 自定义 Hook
export const useEventBus = () => {
const bus = useRef(createEventBus());
// 提供对外接口
const subscribe = useCallback((event, cb) => bus.current.subscribe(event, cb), []);
const publish = useCallback((event, data) => bus.current.publish(event, data), []);
return { subscribe, publish };
};
三、使用场景示例
1. 基础通信
// 发布者组件
function Publisher() {
const { publish } = useEventBus();
return (
<button onClick={() => publish('data-update', { value: Math.random() })}>
发布随机数据
</button>
);
}
// 订阅者组件
function Subscriber() {
const [data, setData] = useState(null);
const { subscribe } = useEventBus();
useEffect(() => {
const unsubscribe = subscribe('data-update', (payload) => {
setData(payload.value);
});
return () => unsubscribe();
}, [subscribe]);
return <div>最新数据: {data}</div>;
}
2. 带参数的事件
// 带参数的订阅
const { subscribe } = useEventBus();
subscribe('user-login', (user) => {
console.log('用户登录:', user);
});
// 带参数的发布
const { publish } = useEventBus();
publish('user-login', { name: 'Alice', role: 'admin' });
四、高级优化技巧
- 类型安全(TypeScript)
interface EventBus {
[key: string]: Array<(data: any) => void>;
}
const createEventBus = (): { subscribe: (e: string, cb: Function) => () => void; publish: (e: string, d: any) => void } => {
const events: EventBus = {};
// ... 实现逻辑
};
- 防抖处理
const publish = useCallback(debounce((event, data) => {
bus.current.publish(event, data);
}, 300), []);
- 批量更新
const publishBatch = useCallback((events) => {
Object.entries(events).forEach(([event, data]) => {
bus.current.publish(event, data);
});
}, []);
五、最佳实践
- 命名规范:使用
domain:action
格式(如user:login
) - 依赖管理:在订阅时指定依赖项数组,避免无效渲染
- 性能监控:通过 React DevTools 检查订阅回调的执行频率
- 错误边界:为订阅回调添加错误处理
useEffect(() => {
const unsubscribe = subscribe('event', (data) => {
try {
// 业务逻辑
} catch (err) {
console.error('事件处理出错:', err);
}
});
// ... 清理逻辑
}, []);
六、对比其他方案
方案 | 优点 | 缺点 |
---|---|---|
总线模式 | 解耦性强,适合复杂场景 | 需要手动管理订阅生命周期 |
Context API | 原生支持,简单易用 | 深层组件更新可能触发无关渲染 |
Redux | 状态管理集中化 | 学习成本高,过度设计简单场景 |
通过这种总线模式的实现,可以在保持组件独立性的同时,实现高效的事件通信。实际项目中建议配合 TypeScript 使用,并通过封装工具函数简化调用流程。对于大型应用,可考虑结合 Context API 实现分层事件总线管理。