xxf_react 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,176 @@
1
+ /**
2
+ * 事件处理器类型
3
+ */
4
+ type Handler<T> = (event: T) => void;
5
+ /**
6
+ * 类构造器类型
7
+ */
8
+ type Constructor<T = any> = new (...args: any[]) => T;
9
+ /**
10
+ * 事件基类
11
+ * 所有事件都需要继承此类
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * export class PaymentSuccessEvent extends BaseEvent {
16
+ * static readonly type = 'PaymentSuccessEvent'
17
+ * constructor(public orderId: string, public amount: number) {
18
+ * super()
19
+ * }
20
+ * }
21
+ * ```
22
+ */
23
+ export declare abstract class BaseEvent {
24
+ /**
25
+ * 事件类型标识(子类必须覆盖此属性)
26
+ * 使用静态属性避免生产环境类名混淆问题
27
+ */
28
+ static readonly type: string;
29
+ /**
30
+ * 获取事件类型标识
31
+ */
32
+ static getType(): string;
33
+ /**
34
+ * 发送当前事件
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * // 发送事件
39
+ * new PaymentSuccessEvent('order-123', 99.99).post()
40
+ *
41
+ * // 监听方式1:直接订阅
42
+ * eventBus.on(PaymentSuccessEvent, (event) => {
43
+ * console.log(event.orderId, event.amount)
44
+ * })
45
+ *
46
+ * // 监听方式2:React Hook
47
+ * useEvent(PaymentSuccessEvent, (event) => {
48
+ * console.log(event.orderId, event.amount)
49
+ * })
50
+ *
51
+ * // 监听方式3:一次性监听
52
+ * useEventOnce(PaymentSuccessEvent, (event) => {
53
+ * console.log('只触发一次')
54
+ * })
55
+ * ```
56
+ */
57
+ post(): Promise<void>;
58
+ }
59
+ /**
60
+ * 事件总线
61
+ * 基于 BroadcastChannel 实现跨浏览器 Tab 通信
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * // 发送事件
66
+ * eventBus.emit(new PaymentSuccessEvent('order-123', 99.99))
67
+ *
68
+ * // 订阅事件
69
+ * eventBus.on(PaymentSuccessEvent, (event) => {
70
+ * console.log(event.orderId)
71
+ * })
72
+ * ```
73
+ */
74
+ declare class EventBus {
75
+ private static instance;
76
+ private channel;
77
+ private handlers;
78
+ private eventRegistry;
79
+ private isInitialized;
80
+ private channelName;
81
+ private constructor();
82
+ /**
83
+ * 获取单例实例
84
+ * @param name 频道名称,默认 'nexus-app'
85
+ */
86
+ static getInstance(name?: string): EventBus;
87
+ /**
88
+ * 初始化频道(延迟初始化,仅在浏览器环境)
89
+ */
90
+ private ensureInitialized;
91
+ /**
92
+ * 分发事件到对应的处理器(用于跨 Tab 消息)
93
+ */
94
+ private dispatch;
95
+ /**
96
+ * 调用事件处理器
97
+ */
98
+ private invokeHandlers;
99
+ /**
100
+ * 发送事件(同时触发本地和跨 Tab 监听)
101
+ * @param event 事件实例
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * eventBus.emit(new PaymentSuccessEvent('order-123', 99.99))
106
+ * ```
107
+ */
108
+ emit<T extends BaseEvent>(event: T): Promise<void>;
109
+ /**
110
+ * 订阅事件
111
+ * @param EventClass 事件类
112
+ * @param handler 事件处理器
113
+ * @returns 取消订阅函数
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * // 订阅
118
+ * const unsubscribe = eventBus.on(PaymentSuccessEvent, (event) => {
119
+ * console.log(event.orderId)
120
+ * })
121
+ *
122
+ * // 取消订阅
123
+ * unsubscribe()
124
+ * ```
125
+ */
126
+ on<T extends BaseEvent>(EventClass: Constructor<T>, handler: Handler<T>): () => void;
127
+ /**
128
+ * 取消订阅事件
129
+ * @param EventClass 事件类
130
+ * @param handler 事件处理器
131
+ */
132
+ off<T extends BaseEvent>(EventClass: Constructor<T>, handler: Handler<T>): void;
133
+ /**
134
+ * 订阅一次性事件(触发后自动取消订阅)
135
+ * @param EventClass 事件类
136
+ * @param handler 事件处理器
137
+ * @returns 取消订阅函数
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * eventBus.once(PaymentSuccessEvent, (event) => {
142
+ * console.log('只触发一次:', event.orderId)
143
+ * })
144
+ * ```
145
+ */
146
+ once<T extends BaseEvent>(EventClass: Constructor<T>, handler: Handler<T>): () => void;
147
+ /**
148
+ * 检查是否有订阅者
149
+ * @param EventClass 事件类
150
+ */
151
+ hasSubscribers<T extends BaseEvent>(EventClass: Constructor<T>): boolean;
152
+ /**
153
+ * 获取订阅者数量
154
+ * @param EventClass 事件类
155
+ */
156
+ subscriberCount<T extends BaseEvent>(EventClass: Constructor<T>): number;
157
+ /**
158
+ * 清除指定事件的所有订阅
159
+ * @param EventClass 事件类
160
+ */
161
+ clear<T extends BaseEvent>(EventClass: Constructor<T>): void;
162
+ /**
163
+ * 清除所有订阅
164
+ */
165
+ clearAll(): void;
166
+ /**
167
+ * 关闭频道并释放资源
168
+ */
169
+ destroy(): Promise<void>;
170
+ }
171
+ /**
172
+ * 事件总线单例
173
+ */
174
+ export declare const eventBus: EventBus;
175
+ export {};
176
+ //# sourceMappingURL=EventBus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventBus.d.ts","sourceRoot":"","sources":["../../src/event-bus/EventBus.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAA;AAEpC;;GAEG;AACH,KAAK,WAAW,CAAC,CAAC,GAAG,GAAG,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;AAErD;;;;;;;;;;;;;GAaG;AACH,8BAAsB,SAAS;IAC3B;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAE5B;;OAEG;IACH,MAAM,CAAC,OAAO,IAAI,MAAM;IAUxB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAGxB;AAED;;;;;;;;;;;;;;GAcG;AACH,cAAM,QAAQ;IACV,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAwB;IAE/C,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,aAAa,CAA4C;IACjE,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,WAAW,CAAQ;IAE3B,OAAO;IAIP;;;OAGG;IACH,MAAM,CAAC,WAAW,CAAC,IAAI,SAAc,GAAG,QAAQ;IAOhD;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAazB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAYhB;;OAEG;IACH,OAAO,CAAC,cAAc;IAatB;;;;;;;;OAQG;IACH,IAAI,CAAC,CAAC,SAAS,SAAS,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBlD;;;;;;;;;;;;;;;;OAgBG;IACH,EAAE,CAAC,CAAC,SAAS,SAAS,EAClB,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,EAC1B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GACpB,MAAM,IAAI;IAoBb;;;;OAIG;IACH,GAAG,CAAC,CAAC,SAAS,SAAS,EACnB,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,EAC1B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GACpB,IAAI;IAYP;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,CAAC,SAAS,SAAS,EACpB,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,EAC1B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GACpB,MAAM,IAAI;IAQb;;;OAGG;IACH,cAAc,CAAC,CAAC,SAAS,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO;IAMxE;;;OAGG;IACH,eAAe,CAAC,CAAC,SAAS,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM;IAKxE;;;OAGG;IACH,KAAK,CAAC,CAAC,SAAS,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI;IAM5D;;OAEG;IACH,QAAQ,IAAI,IAAI;IAKhB;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CASjC;AAED;;GAEG;AACH,eAAO,MAAM,QAAQ,UAAyB,CAAA"}
@@ -0,0 +1,278 @@
1
+ import { BroadcastChannel } from 'broadcast-channel';
2
+ /**
3
+ * 事件基类
4
+ * 所有事件都需要继承此类
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * export class PaymentSuccessEvent extends BaseEvent {
9
+ * static readonly type = 'PaymentSuccessEvent'
10
+ * constructor(public orderId: string, public amount: number) {
11
+ * super()
12
+ * }
13
+ * }
14
+ * ```
15
+ */
16
+ export class BaseEvent {
17
+ /**
18
+ * 获取事件类型标识
19
+ */
20
+ static getType() {
21
+ if (!this.type) {
22
+ throw new Error(`[EventBus] Event class "${this.name}" must define a static "type" property. ` +
23
+ `Example: static readonly type = '${this.name}'`);
24
+ }
25
+ return this.type;
26
+ }
27
+ /**
28
+ * 发送当前事件
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * // 发送事件
33
+ * new PaymentSuccessEvent('order-123', 99.99).post()
34
+ *
35
+ * // 监听方式1:直接订阅
36
+ * eventBus.on(PaymentSuccessEvent, (event) => {
37
+ * console.log(event.orderId, event.amount)
38
+ * })
39
+ *
40
+ * // 监听方式2:React Hook
41
+ * useEvent(PaymentSuccessEvent, (event) => {
42
+ * console.log(event.orderId, event.amount)
43
+ * })
44
+ *
45
+ * // 监听方式3:一次性监听
46
+ * useEventOnce(PaymentSuccessEvent, (event) => {
47
+ * console.log('只触发一次')
48
+ * })
49
+ * ```
50
+ */
51
+ post() {
52
+ return eventBus.emit(this);
53
+ }
54
+ }
55
+ /**
56
+ * 事件总线
57
+ * 基于 BroadcastChannel 实现跨浏览器 Tab 通信
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * // 发送事件
62
+ * eventBus.emit(new PaymentSuccessEvent('order-123', 99.99))
63
+ *
64
+ * // 订阅事件
65
+ * eventBus.on(PaymentSuccessEvent, (event) => {
66
+ * console.log(event.orderId)
67
+ * })
68
+ * ```
69
+ */
70
+ class EventBus {
71
+ constructor(name) {
72
+ this.channel = null;
73
+ this.handlers = new Map();
74
+ this.eventRegistry = new Map();
75
+ this.isInitialized = false;
76
+ this.channelName = name;
77
+ }
78
+ /**
79
+ * 获取单例实例
80
+ * @param name 频道名称,默认 'nexus-app'
81
+ */
82
+ static getInstance(name = 'nexus-app') {
83
+ if (!EventBus.instance) {
84
+ EventBus.instance = new EventBus(name);
85
+ }
86
+ return EventBus.instance;
87
+ }
88
+ /**
89
+ * 初始化频道(延迟初始化,仅在浏览器环境)
90
+ */
91
+ ensureInitialized() {
92
+ if (this.isInitialized)
93
+ return;
94
+ // 仅在浏览器环境初始化
95
+ if (typeof window === 'undefined')
96
+ return;
97
+ this.channel = new BroadcastChannel(this.channelName);
98
+ this.channel.onmessage = (msg) => {
99
+ this.dispatch(msg.type, msg.data);
100
+ };
101
+ this.isInitialized = true;
102
+ }
103
+ /**
104
+ * 分发事件到对应的处理器(用于跨 Tab 消息)
105
+ */
106
+ dispatch(type, data) {
107
+ const EventClass = this.eventRegistry.get(type);
108
+ if (!EventClass)
109
+ return;
110
+ const handlers = this.handlers.get(type);
111
+ if (!handlers || handlers.size === 0)
112
+ return;
113
+ // 还原事件对象
114
+ const event = Object.assign(Object.create(EventClass.prototype), data);
115
+ this.invokeHandlers(type, event);
116
+ }
117
+ /**
118
+ * 调用事件处理器
119
+ */
120
+ invokeHandlers(type, event) {
121
+ const handlers = this.handlers.get(type);
122
+ if (!handlers || handlers.size === 0)
123
+ return;
124
+ handlers.forEach(handler => {
125
+ try {
126
+ handler(event);
127
+ }
128
+ catch (error) {
129
+ console.error(`[EventBus] Error in handler for ${type}:`, error);
130
+ }
131
+ });
132
+ }
133
+ /**
134
+ * 发送事件(同时触发本地和跨 Tab 监听)
135
+ * @param event 事件实例
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * eventBus.emit(new PaymentSuccessEvent('order-123', 99.99))
140
+ * ```
141
+ */
142
+ emit(event) {
143
+ this.ensureInitialized();
144
+ const EventClass = event.constructor;
145
+ const type = EventClass.getType();
146
+ // 1. 触发本地 handlers(当前 Tab)
147
+ this.invokeHandlers(type, event);
148
+ // 2. 发送到 BroadcastChannel(其他 Tab)
149
+ if (!this.channel) {
150
+ return Promise.resolve();
151
+ }
152
+ // 序列化事件数据(排除原型方法)
153
+ const data = Object.keys(event).reduce((acc, key) => {
154
+ acc[key] = event[key];
155
+ return acc;
156
+ }, {});
157
+ return this.channel.postMessage({ type, data });
158
+ }
159
+ /**
160
+ * 订阅事件
161
+ * @param EventClass 事件类
162
+ * @param handler 事件处理器
163
+ * @returns 取消订阅函数
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * // 订阅
168
+ * const unsubscribe = eventBus.on(PaymentSuccessEvent, (event) => {
169
+ * console.log(event.orderId)
170
+ * })
171
+ *
172
+ * // 取消订阅
173
+ * unsubscribe()
174
+ * ```
175
+ */
176
+ on(EventClass, handler) {
177
+ this.ensureInitialized();
178
+ const type = EventClass.getType();
179
+ // 注册事件类
180
+ if (!this.eventRegistry.has(type)) {
181
+ this.eventRegistry.set(type, EventClass);
182
+ }
183
+ // 添加处理器
184
+ if (!this.handlers.has(type)) {
185
+ this.handlers.set(type, new Set());
186
+ }
187
+ this.handlers.get(type).add(handler);
188
+ // 返回取消订阅函数
189
+ return () => this.off(EventClass, handler);
190
+ }
191
+ /**
192
+ * 取消订阅事件
193
+ * @param EventClass 事件类
194
+ * @param handler 事件处理器
195
+ */
196
+ off(EventClass, handler) {
197
+ const type = EventClass.getType();
198
+ const handlers = this.handlers.get(type);
199
+ if (handlers) {
200
+ handlers.delete(handler);
201
+ // 清理空 Set 避免内存泄漏
202
+ if (handlers.size === 0) {
203
+ this.handlers.delete(type);
204
+ }
205
+ }
206
+ }
207
+ /**
208
+ * 订阅一次性事件(触发后自动取消订阅)
209
+ * @param EventClass 事件类
210
+ * @param handler 事件处理器
211
+ * @returns 取消订阅函数
212
+ *
213
+ * @example
214
+ * ```ts
215
+ * eventBus.once(PaymentSuccessEvent, (event) => {
216
+ * console.log('只触发一次:', event.orderId)
217
+ * })
218
+ * ```
219
+ */
220
+ once(EventClass, handler) {
221
+ const wrapper = (event) => {
222
+ this.off(EventClass, wrapper);
223
+ handler(event);
224
+ };
225
+ return this.on(EventClass, wrapper);
226
+ }
227
+ /**
228
+ * 检查是否有订阅者
229
+ * @param EventClass 事件类
230
+ */
231
+ hasSubscribers(EventClass) {
232
+ const type = EventClass.getType();
233
+ const handlers = this.handlers.get(type);
234
+ return !!handlers && handlers.size > 0;
235
+ }
236
+ /**
237
+ * 获取订阅者数量
238
+ * @param EventClass 事件类
239
+ */
240
+ subscriberCount(EventClass) {
241
+ var _a, _b;
242
+ const type = EventClass.getType();
243
+ return (_b = (_a = this.handlers.get(type)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0;
244
+ }
245
+ /**
246
+ * 清除指定事件的所有订阅
247
+ * @param EventClass 事件类
248
+ */
249
+ clear(EventClass) {
250
+ const type = EventClass.getType();
251
+ this.handlers.delete(type);
252
+ this.eventRegistry.delete(type);
253
+ }
254
+ /**
255
+ * 清除所有订阅
256
+ */
257
+ clearAll() {
258
+ this.handlers.clear();
259
+ this.eventRegistry.clear();
260
+ }
261
+ /**
262
+ * 关闭频道并释放资源
263
+ */
264
+ async destroy() {
265
+ this.clearAll();
266
+ if (this.channel) {
267
+ await this.channel.close();
268
+ this.channel = null;
269
+ }
270
+ this.isInitialized = false;
271
+ EventBus.instance = null;
272
+ }
273
+ }
274
+ EventBus.instance = null;
275
+ /**
276
+ * 事件总线单例
277
+ */
278
+ export const eventBus = EventBus.getInstance();
@@ -0,0 +1,71 @@
1
+ import { BaseEvent } from './index';
2
+ /**
3
+ * 支付成功事件
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * // 发送
8
+ * new PaymentSuccessEvent('order-123', 99.99).post()
9
+ *
10
+ * // 监听
11
+ * useEvent(PaymentSuccessEvent, (e) => console.log(e.orderId, e.amount))
12
+ * ```
13
+ */
14
+ export declare class PaymentSuccessEvent extends BaseEvent {
15
+ orderId: string;
16
+ amount: number;
17
+ static readonly type = "PaymentSuccessEvent";
18
+ constructor(orderId: string, amount: number);
19
+ }
20
+ /**
21
+ * 用户登录事件
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * // 发送
26
+ * new LoginEvent('user-123', 'john@example.com').post()
27
+ *
28
+ * // 监听
29
+ * useEvent(LoginEvent, (e) => console.log(e.userId, e.email))
30
+ * ```
31
+ */
32
+ export declare class LoginEvent extends BaseEvent {
33
+ userId: string;
34
+ email?: string | undefined;
35
+ static readonly type = "LoginEvent";
36
+ constructor(userId: string, email?: string | undefined);
37
+ }
38
+ /**
39
+ * 用户登出事件
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * // 发送
44
+ * new LogoutEvent().post()
45
+ *
46
+ * // 监听
47
+ * useEvent(LogoutEvent, () => console.log('用户已登出'))
48
+ * ```
49
+ */
50
+ export declare class LogoutEvent extends BaseEvent {
51
+ static readonly type = "LogoutEvent";
52
+ }
53
+ /**
54
+ * 用户积分变更事件
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * // 发送
59
+ * new CreditsChangedEvent(100, 200).post()
60
+ *
61
+ * // 监听
62
+ * useEvent(CreditsChangedEvent, (e) => console.log(e.previousCredits, e.currentCredits))
63
+ * ```
64
+ */
65
+ export declare class CreditsChangedEvent extends BaseEvent {
66
+ previousCredits: number;
67
+ currentCredits: number;
68
+ static readonly type = "CreditsChangedEvent";
69
+ constructor(previousCredits: number, currentCredits: number);
70
+ }
71
+ //# sourceMappingURL=EventDemo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventDemo.d.ts","sourceRoot":"","sources":["../../src/event-bus/EventDemo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEnC;;;;;;;;;;;GAWG;AACH,qBAAa,mBAAoB,SAAQ,SAAS;IAIvC,OAAO,EAAE,MAAM;IACf,MAAM,EAAE,MAAM;IAJvB,MAAM,CAAC,QAAQ,CAAC,IAAI,yBAAwB;gBAGnC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM;CAIxB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,UAAW,SAAQ,SAAS;IAI9B,MAAM,EAAE,MAAM;IACd,KAAK,CAAC,EAAE,MAAM;IAJvB,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAe;gBAG1B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,YAAA;CAIxB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,WAAY,SAAQ,SAAS;IACxC,MAAM,CAAC,QAAQ,CAAC,IAAI,iBAAgB;CACrC;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,mBAAoB,SAAQ,SAAS;IAIvC,eAAe,EAAE,MAAM;IACvB,cAAc,EAAE,MAAM;IAJ/B,MAAM,CAAC,QAAQ,CAAC,IAAI,yBAAwB;gBAGnC,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,MAAM;CAIhC"}
@@ -0,0 +1,76 @@
1
+ import { BaseEvent } from './index';
2
+ /**
3
+ * 支付成功事件
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * // 发送
8
+ * new PaymentSuccessEvent('order-123', 99.99).post()
9
+ *
10
+ * // 监听
11
+ * useEvent(PaymentSuccessEvent, (e) => console.log(e.orderId, e.amount))
12
+ * ```
13
+ */
14
+ export class PaymentSuccessEvent extends BaseEvent {
15
+ constructor(orderId, amount) {
16
+ super();
17
+ this.orderId = orderId;
18
+ this.amount = amount;
19
+ }
20
+ }
21
+ PaymentSuccessEvent.type = 'PaymentSuccessEvent';
22
+ /**
23
+ * 用户登录事件
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * // 发送
28
+ * new LoginEvent('user-123', 'john@example.com').post()
29
+ *
30
+ * // 监听
31
+ * useEvent(LoginEvent, (e) => console.log(e.userId, e.email))
32
+ * ```
33
+ */
34
+ export class LoginEvent extends BaseEvent {
35
+ constructor(userId, email) {
36
+ super();
37
+ this.userId = userId;
38
+ this.email = email;
39
+ }
40
+ }
41
+ LoginEvent.type = 'LoginEvent';
42
+ /**
43
+ * 用户登出事件
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * // 发送
48
+ * new LogoutEvent().post()
49
+ *
50
+ * // 监听
51
+ * useEvent(LogoutEvent, () => console.log('用户已登出'))
52
+ * ```
53
+ */
54
+ export class LogoutEvent extends BaseEvent {
55
+ }
56
+ LogoutEvent.type = 'LogoutEvent';
57
+ /**
58
+ * 用户积分变更事件
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * // 发送
63
+ * new CreditsChangedEvent(100, 200).post()
64
+ *
65
+ * // 监听
66
+ * useEvent(CreditsChangedEvent, (e) => console.log(e.previousCredits, e.currentCredits))
67
+ * ```
68
+ */
69
+ export class CreditsChangedEvent extends BaseEvent {
70
+ constructor(previousCredits, currentCredits) {
71
+ super();
72
+ this.previousCredits = previousCredits;
73
+ this.currentCredits = currentCredits;
74
+ }
75
+ }
76
+ CreditsChangedEvent.type = 'CreditsChangedEvent';
@@ -0,0 +1,48 @@
1
+ import { BaseEvent } from './index';
2
+ type Constructor<T = any> = new (...args: any[]) => T;
3
+ /**
4
+ * 订阅事件的 React Hook
5
+ * 自动在组件卸载时取消订阅
6
+ *
7
+ * handler 会自动获取最新闭包值,无需手动管理依赖
8
+ *
9
+ * @param EventClass 事件类
10
+ * @param handler 事件处理器
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * function PaymentListener() {
15
+ * const [count, setCount] = useState(0)
16
+ *
17
+ * useEvent(PaymentSuccessEvent, (event) => {
18
+ * // 可以安全访问最新的 count 值
19
+ * console.log('当前计数:', count)
20
+ * console.log('收到支付成功事件:', event.orderId)
21
+ * })
22
+ *
23
+ * return <div>Listening...</div>
24
+ * }
25
+ * ```
26
+ */
27
+ export declare function useEvent<T extends BaseEvent>(EventClass: Constructor<T>, handler: (event: T) => void): void;
28
+ /**
29
+ * 订阅一次性事件的 React Hook
30
+ * 事件触发一次后自动取消订阅
31
+ *
32
+ * @param EventClass 事件类
33
+ * @param handler 事件处理器
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * function OneTimeListener() {
38
+ * useEventOnce(PaymentSuccessEvent, (event) => {
39
+ * console.log('只触发一次:', event.orderId)
40
+ * })
41
+ *
42
+ * return <div>Waiting for one event...</div>
43
+ * }
44
+ * ```
45
+ */
46
+ export declare function useEventOnce<T extends BaseEvent>(EventClass: Constructor<T>, handler: (event: T) => void): void;
47
+ export {};
48
+ //# sourceMappingURL=EventHooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventHooks.d.ts","sourceRoot":"","sources":["../../src/event-bus/EventHooks.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,SAAS,EAAE,MAAM,SAAS,CAAA;AAE7C,KAAK,WAAW,CAAC,CAAC,GAAG,GAAG,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;AAErD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,SAAS,EAC1C,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAC1B,IAAI,CAaN;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,SAAS,EAC9C,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAC1B,IAAI,CAoBN"}
@@ -0,0 +1,73 @@
1
+ import { useEffect, useCallback, useRef } from 'react';
2
+ import { eventBus } from './index';
3
+ /**
4
+ * 订阅事件的 React Hook
5
+ * 自动在组件卸载时取消订阅
6
+ *
7
+ * handler 会自动获取最新闭包值,无需手动管理依赖
8
+ *
9
+ * @param EventClass 事件类
10
+ * @param handler 事件处理器
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * function PaymentListener() {
15
+ * const [count, setCount] = useState(0)
16
+ *
17
+ * useEvent(PaymentSuccessEvent, (event) => {
18
+ * // 可以安全访问最新的 count 值
19
+ * console.log('当前计数:', count)
20
+ * console.log('收到支付成功事件:', event.orderId)
21
+ * })
22
+ *
23
+ * return <div>Listening...</div>
24
+ * }
25
+ * ```
26
+ */
27
+ export function useEvent(EventClass, handler) {
28
+ // 使用 ref 保存最新的 handler,避免频繁订阅/取消订阅
29
+ const handlerRef = useRef(handler);
30
+ handlerRef.current = handler;
31
+ // 稳定的回调函数,始终调用最新的 handler
32
+ const stableHandler = useCallback((event) => {
33
+ handlerRef.current(event);
34
+ }, []);
35
+ useEffect(() => {
36
+ return eventBus.on(EventClass, stableHandler);
37
+ }, [EventClass, stableHandler]);
38
+ }
39
+ /**
40
+ * 订阅一次性事件的 React Hook
41
+ * 事件触发一次后自动取消订阅
42
+ *
43
+ * @param EventClass 事件类
44
+ * @param handler 事件处理器
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * function OneTimeListener() {
49
+ * useEventOnce(PaymentSuccessEvent, (event) => {
50
+ * console.log('只触发一次:', event.orderId)
51
+ * })
52
+ *
53
+ * return <div>Waiting for one event...</div>
54
+ * }
55
+ * ```
56
+ */
57
+ export function useEventOnce(EventClass, handler) {
58
+ // 使用 ref 保存最新的 handler
59
+ const handlerRef = useRef(handler);
60
+ handlerRef.current = handler;
61
+ // 追踪是否已触发,避免组件卸载后执行
62
+ const firedRef = useRef(false);
63
+ useEffect(() => {
64
+ firedRef.current = false;
65
+ const stableHandler = (event) => {
66
+ if (!firedRef.current) {
67
+ firedRef.current = true;
68
+ handlerRef.current(event);
69
+ }
70
+ };
71
+ return eventBus.once(EventClass, stableHandler);
72
+ }, [EventClass]);
73
+ }
@@ -0,0 +1,3 @@
1
+ export * from './EventBus';
2
+ export * from './EventHooks';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/event-bus/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './EventBus';
2
+ export * from './EventHooks';
package/dist/index.d.ts CHANGED
@@ -8,5 +8,6 @@ export * from './flow';
8
8
  export * from './responsive';
9
9
  export * from './foundation';
10
10
  export * from './layout';
11
+ export * from './event-bus';
11
12
  export declare function initXXF(): void;
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AAGzB,wBAAgB,OAAO,SAEtB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAG5B,wBAAgB,OAAO,SAEtB"}
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ export * from './flow';
9
9
  export * from './responsive';
10
10
  export * from './foundation';
11
11
  export * from './layout';
12
+ export * from './event-bus';
12
13
  ///框架初始化函数,调用后会初始化一些全局功能
13
14
  export function initXXF() {
14
15
  initPromiseErrorExtension();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xxf_react",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -49,6 +49,10 @@
49
49
  "./layout": {
50
50
  "types": "./dist/layout/index.d.ts",
51
51
  "import": "./dist/layout/index.js"
52
+ },
53
+ "./event-bus": {
54
+ "types": "./dist/event-bus/index.d.ts",
55
+ "import": "./dist/event-bus/index.js"
52
56
  }
53
57
  },
54
58
  "files": [
@@ -64,8 +68,8 @@
64
68
  "@microsoft/fetch-event-source": "^2.0.1",
65
69
  "@use-gesture/react": "^10.3.1",
66
70
  "bowser": "^2.14.1",
71
+ "broadcast-channel": "^7.3.0",
67
72
  "dayjs": "^1.11.19",
68
- "mitt": "^3.0.1",
69
73
  "react-async-hook": "^4.0.0",
70
74
  "react-resize-detector": "^12.3.0",
71
75
  "react-responsive": "^10.0.1",
@@ -1,60 +0,0 @@
1
- import { ReactNode, CSSProperties, ElementType, ComponentPropsWithoutRef } from 'react';
2
- import React from "react";
3
- export interface ContainerSize {
4
- width: number;
5
- height: number;
6
- }
7
- interface SizedContainerProps<T extends ElementType = 'div'> {
8
- /** 子元素渲染函数,接收容器尺寸和就绪状态 */
9
- children: (size: ContainerSize & {
10
- isReady: boolean;
11
- }) => ReactNode;
12
- /** 容器的 className */
13
- className?: string;
14
- /** 容器的 style */
15
- style?: CSSProperties;
16
- /** 自定义容器元素类型,默认 div */
17
- as?: T;
18
- /** 是否只监听宽度变化 */
19
- widthOnly?: boolean;
20
- /** 是否只监听高度变化 */
21
- heightOnly?: boolean;
22
- /** 防抖延迟(毫秒) */
23
- debounce?: number;
24
- /** 是否跳过首次挂载时的计算 */
25
- skipOnMount?: boolean;
26
- /** 尺寸未就绪时显示的占位内容 */
27
- fallback?: ReactNode;
28
- /** 尺寸变化时的回调 */
29
- onResize?: (size: ContainerSize) => void;
30
- }
31
- /**
32
- * 自动测量尺寸的容器组件
33
- *
34
- * @example
35
- * // 基础用法
36
- * <SizedContainer className="h-full w-full">
37
- * {({ width, height, isReady }) => (
38
- * <VirtuosoGrid style={{ width, height }} ... />
39
- * )}
40
- * </SizedContainer>
41
- *
42
- * @example
43
- * // 只监听高度,带防抖和占位符
44
- * <SizedContainer
45
- * heightOnly
46
- * debounce={100}
47
- * fallback={<Skeleton />}
48
- * >
49
- * {({ height }) => <List style={{ height }} />}
50
- * </SizedContainer>
51
- *
52
- * @example
53
- * // 自定义元素类型
54
- * <SizedContainer as="section" className="h-full">
55
- * {({ width, height }) => <Chart width={width} height={height} />}
56
- * </SizedContainer>
57
- */
58
- export declare function SizedContainer<T extends ElementType = 'div'>({ children, className, style, as, widthOnly, heightOnly, debounce, skipOnMount, fallback, onResize, }: SizedContainerProps<T> & Omit<ComponentPropsWithoutRef<T>, keyof SizedContainerProps<T>>): React.JSX.Element;
59
- export {};
60
- //# sourceMappingURL=SizedContainer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SizedContainer.d.ts","sourceRoot":"","sources":["../../src/utils/SizedContainer.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,OAAO,CAAC;AACxF,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,mBAAmB,CAAC,CAAC,SAAS,WAAW,GAAG,KAAK;IACvD,0BAA0B;IAC1B,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,GAAG;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,SAAS,CAAC;IACpE,oBAAoB;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB;IAChB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,uBAAuB;IACvB,EAAE,CAAC,EAAE,CAAC,CAAC;IACP,gBAAgB;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,eAAe;IACf,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CAC5C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,GAAG,KAAK,EAAE,EACI,QAAQ,EACR,SAAS,EACT,KAAK,EACL,EAAE,EACF,SAAiB,EACjB,UAAkB,EAClB,QAAQ,EACR,WAAmB,EACnB,QAAQ,EACR,QAAQ,GACX,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,MAAM,mBAAmB,CAAC,CAAC,CAAC,CAAC,qBAmCxJ"}
@@ -1,60 +0,0 @@
1
- 'use client';
2
- import { useResizeDetector } from 'react-resize-detector';
3
- import React from "react";
4
- /**
5
- * 自动测量尺寸的容器组件
6
- *
7
- * @example
8
- * // 基础用法
9
- * <SizedContainer className="h-full w-full">
10
- * {({ width, height, isReady }) => (
11
- * <VirtuosoGrid style={{ width, height }} ... />
12
- * )}
13
- * </SizedContainer>
14
- *
15
- * @example
16
- * // 只监听高度,带防抖和占位符
17
- * <SizedContainer
18
- * heightOnly
19
- * debounce={100}
20
- * fallback={<Skeleton />}
21
- * >
22
- * {({ height }) => <List style={{ height }} />}
23
- * </SizedContainer>
24
- *
25
- * @example
26
- * // 自定义元素类型
27
- * <SizedContainer as="section" className="h-full">
28
- * {({ width, height }) => <Chart width={width} height={height} />}
29
- * </SizedContainer>
30
- */
31
- export function SizedContainer({ children, className, style, as, widthOnly = false, heightOnly = false, debounce, skipOnMount = false, fallback, onResize, }) {
32
- const { width, height, ref } = useResizeDetector({
33
- handleWidth: !heightOnly,
34
- handleHeight: !widthOnly,
35
- refreshMode: debounce ? 'debounce' : undefined,
36
- refreshRate: debounce,
37
- skipOnMount,
38
- onResize: onResize
39
- ? ({ width: w, height: h }) => {
40
- if (w !== null && h !== null) {
41
- onResize({ width: w, height: h });
42
- }
43
- }
44
- : undefined,
45
- });
46
- const size = {
47
- width: width !== null && width !== void 0 ? width : 0,
48
- height: height !== null && height !== void 0 ? height : 0,
49
- };
50
- // 根据监听模式判断是否就绪
51
- const isReady = (() => {
52
- if (widthOnly)
53
- return size.width > 0;
54
- if (heightOnly)
55
- return size.height > 0;
56
- return size.width > 0 && size.height > 0;
57
- })();
58
- const Component = as || 'div';
59
- return (React.createElement(Component, { ref: ref, className: className, style: style }, isReady ? children({ ...size, isReady }) : fallback));
60
- }