正在加载文档...
文档内容较大,正在处理中,请稍候
正在加载文档...
文档内容较大,正在处理中,请稍候
发布订阅事件总线系统是一个基于 React Hooks 的全局事件通信机制,采用发布-订阅模式(Pub-Sub Pattern),实现组件间的解耦通信。系统通过 useEventBus Hook 提供统一的事件发布、订阅和管理功能。
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 发布者组件 │──────▶│ useEventBus │──────▶│ 订阅者组件 │
│ (Publisher) │ │ (事件总线) │ │ (Subscriber) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Window Events │
│ (CustomEvent) │
└─────────────────┘export const EVENT_TYPES = {
AUTH_EXPIRED: "auth:expired", // 登录失效事件
AUTH_RESET: "auth:reset", // 认证重置事件
TOKEN_REFRESH_SUCCESS: "token:refresh:success", // Token 刷新成功事件
ERROR_TIP: "error:tip", // 错误提示事件
GLOBAL_MESSAGE: "global:message", // 全局消息事件
GLOBAL_MODAL: "global:modal", // 全局弹窗事件
GLOBAL_NOTIFICATION: "global:notification", // 全局通知事件
KEEP_ALIVE: "keep:alive", // 保持活跃事件
};import { useEventBus } from "@/hooks/useEventBus";
const { publish, subscribe, unsubscribe, addEventListener } = useEventBus();| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| publish | 发布事件 | eventType: 事件类型payload: 事件数据 | 无 |
| subscribe | 订阅事件 | eventType: 事件类型callback: 回调函数options: 配置项 | 取消订阅的函数 |
| unsubscribe | 取消订阅 | eventType: 事件类型callback: 回调函数 | 无 |
| addEventListener | 监听全局事件 | eventType: 事件类型handler: 事件处理器 | 取消监听的函数 |
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| once | boolean | false | 是否只执行一次订阅 |
import React, { useEffect } from "react";
import { Button } from "antd";
import { useEventBus } from "@/hooks/useEventBus";
// 发布者组件
const PublisherComponent = () => {
const { publish } = useEventBus();
const handlePublish = () => {
publish("custom:event", {
message: "Hello World!",
timestamp: Date.now(),
});
};
return <Button onClick={handlePublish}>发布事件</Button>;
};
// 订阅者组件
const SubscriberComponent = () => {
const { subscribe } = useEventBus();
useEffect(() => {
// 订阅自定义事件
const unsubscribe = subscribe("custom:event", (data) => {
console.log("接收到事件:", data);
// data = { message: "Hello World!", timestamp: 1234567890 }
});
// 组件卸载时取消订阅
return () => unsubscribe();
}, [subscribe]);
return <div>订阅者组件 - 正在监听事件</div>;
};
export default function App() {
return (
<div>
<PublisherComponent />
<SubscriberComponent />
</div>
);
}import React, { useEffect } from "react";
import { useEventBus } from "@/hooks/useEventBus";
const OneTimeSubscriber = () => {
const { subscribe, publish } = useEventBus();
useEffect(() => {
// 一次性订阅,执行后自动取消
const unsubscribe = subscribe(
"one-time:event",
(data) => {
console.log("这个事件只会触发一次:", data);
},
{ once: true }
);
return () => unsubscribe();
}, [subscribe]);
const triggerEvent = () => {
publish("one-time:event", { message: "触发事件" });
// 再次发布不会触发回调
setTimeout(() => {
publish("one-time:event", { message: "不会触发" });
}, 1000);
};
return <button onClick={triggerEvent}>触发一次性事件</button>;
};import React, { useEffect } from "react";
import { useEventBus } from "@/hooks/useEventBus";
const GlobalEventListener = () => {
const { addEventListener } = useEventBus();
useEffect(() => {
// 使用原生 window 事件监听(适用于非 React 环境触发的事件)
const unsubscribe = addEventListener("global:event", (data) => {
console.log("全局事件:", data);
});
return () => unsubscribe();
}, [addEventListener]);
return <div>全局事件监听器</div>;
};import React, { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useEventBus, EVENT_TYPES } from "@/hooks/useEventBus";
// 登录失效处理组件
const LoginExpiredHandler = () => {
const navigate = useNavigate();
const { subscribe } = useEventBus();
useEffect(() => {
// 订阅登录失效事件
const unsubscribe = subscribe(EVENT_TYPES.AUTH_EXPIRED, (detail) => {
console.log("登录已失效:", detail);
// 延迟跳转到登录页,让用户看到提示消息
setTimeout(() => {
navigate("/login");
}, 1500);
});
return () => unsubscribe();
}, [subscribe, navigate]);
return null;
};
// 保持活跃事件处理
const KeepAliveHandler = () => {
const { subscribe } = useEventBus();
useEffect(() => {
// 监听 keepAlive:clear 事件
const unsubscribe = subscribe("keep:alive:clear", (options) => {
console.log("清除缓存:", options);
// 处理缓存清除逻辑
});
return () => unsubscribe();
}, [subscribe]);
return null;
};import React, { useState, useEffect } from "react";
import { Button, Card } from "antd";
import { useEventBus } from "@/hooks/useEventBus";
// 用户信息发布组件
const UserInfoPublisher = () => {
const { publish } = useEventBus();
const updateUserInfo = () => {
publish("user:info:updated", {
userId: 123,
name: "张三",
avatar: "https://example.com/avatar.jpg",
timestamp: Date.now(),
});
};
return (
<Card>
<Button onClick={updateUserInfo}>更新用户信息</Button>
</Card>
);
};
// 用户信息显示组件(可能在应用的任何位置)
const UserInfoDisplay = () => {
const { subscribe } = useEventBus();
const [userInfo, setUserInfo] = useState(null);
useEffect(() => {
const unsubscribe = subscribe("user:info:updated", (data) => {
setUserInfo(data);
});
return () => unsubscribe();
}, [subscribe]);
if (!userInfo) {
return <div>等待用户信息更新...</div>;
}
return (
<Card>
<div>用户ID: {userInfo.userId}</div>
<div>姓名: {userInfo.name}</div>
<div>更新时间: {new Date(userInfo.timestamp).toLocaleString()}</div>
</Card>
);
};
// 应用组件
const App = () => {
return (
<div>
<UserInfoPublisher />
<UserInfoDisplay />
</div>
);
};通过 globalMessageUtils 可以在非 React 组件环境中发布事件:
import { globalMessageUtils } from "@/hooks/useGlobalMessage";
// 发布自定义事件
globalMessageUtils.customEvent("custom:event", { message: "Hello" });
// 发布认证过期事件
globalMessageUtils.authExpired({ content: "登录已失效,请重新登录" });
// 发布保持活跃事件(支持 clear 动作)
globalMessageUtils.keepAlive("clear", { cacheKeys: ["user", "settings"] });// 在任何 JavaScript 代码中直接发布事件
window.dispatchEvent(
new CustomEvent("custom:event", {
detail: { message: "Hello from non-React code" },
})
);
// 监听事件
window.addEventListener("custom:event", (e) => {
console.log("接收到事件:", e.detail);
});当用户登录失效时触发,系统会自动处理:
auth:expiredwindow.__IS_MANUAL_LOGOUT__,则不会显示消息import { globalMessageUtils } from "@/hooks/useGlobalMessage";
// 在 API 拦截器中触发
if (response.status === 401) {
globalMessageUtils.authExpired({ content: "登录已失效,请重新登录" });
}用于触发缓存管理相关操作:
keep:aliveclear(清除缓存)// 发布保持活跃事件,触发清除缓存
globalMessageUtils.keepAlive("clear", {
cacheKeys: ["user", "settings", "cache"],
});
// 在组件中监听
const { subscribe } = useEventBus();
useEffect(() => {
const unsubscribe = subscribe("keep:alive:clear", (options) => {
// 处理缓存清除逻辑
clearCache(options.cacheKeys);
});
return () => unsubscribe();
}, [subscribe]);当 Token 刷新成功时触发:
token:refresh:successconst { publish, EVENT_TYPES } = useEventBus();
// Token 刷新成功后发布事件
publish(EVENT_TYPES.TOKEN_REFRESH_SUCCESS, {
newToken: "new-token-value",
expiresAt: Date.now() + 3600000,
});推荐使用 模块:动作 或 模块:动作:对象 的命名格式:
// 好的命名
"user:login";
"user:logout";
"order:created";
"order:status:updated";
"notification:read";
"notification:read:all";
// 避免的命名
"event1";
"update";
"data";重要:在组件卸载时一定要取消订阅,避免内存泄漏:
useEffect(() => {
const unsubscribe = subscribe("some:event", handler);
// ✅ 正确:返回清理函数
return () => unsubscribe();
}, [subscribe]);对于只需要触发一次的事件,使用 once 选项:
subscribe("page:loaded", handlePageLoad, { once: true });保持事件数据简洁,只包含必要信息:
// ✅ 好的设计
publish("user:updated", {
userId: 123,
changedFields: ["name", "avatar"],
});
// ❌ 避免的设计
publish("user:updated", {
userId: 123,
name: "张三",
email: "zhang@example.com",
avatar: "...",
// ... 包含大量不必要的数据
});可能原因:
解决方法:
// 确保在组件挂载时立即订阅
useEffect(() => {
const unsubscribe = subscribe("event:name", handler);
return () => unsubscribe();
}, []); // 空依赖数组,只在挂载时订阅原因:忘记取消订阅
解决方法:
useEffect(() => {
const unsubscribe = subscribe("event", handler);
return () => unsubscribe(); // 必须返回清理函数
}, [subscribe]);原因:重复订阅或组件多次渲染
解决方法:
// 使用 useRef 确保只订阅一次
const subscribedRef = useRef(false);
useEffect(() => {
if (!subscribedRef.current) {
subscribedRef.current = true;
const unsubscribe = subscribe("event", handler);
return () => {
unsubscribe();
subscribedRef.current = false;
};
}
}, []);| 版本 | 日期 | 变更内容 |
|---|---|---|
| 2.0.0 | 2025-12-05 | 架构重构版本 |
| - 独立的事件总线 Hook | ||
| - 支持一次性订阅 | ||
| - 优化系统级事件处理 | ||
| - 添加 Token 刷新成功事件 | ||
| 1.0.0 | 2025-12-02 | 初始版本发布 |
| - 实现基础发布订阅功能 | ||
| - 支持 CustomEvent |
轮子啊轮子 ✨