| name | event-management |
| description | 使用发射器和一次性订阅管理事件和订阅。处理事件清理、内存泄漏和复杂事件流。 |
事件管理技能
何时使用此技能
当你需要以下操作时使用此技能:
- 创建可观察事件系统(发布-订阅模式)
- 管理订阅生命周期(订阅、取消订阅)
- 防止内存泄漏 来自忘记的监听器
- 处理清理 当组件卸载时
- 链接事件 在复杂流中
- 使用 DOM 或自定义事件 以结构化方式
快速入门
import { Emitter } from '@x-oasis/emitter';
import { DisposableStore } from '@x-oasis/disposable';
const emitter = new Emitter<string>();
const listener = (data: string) => console.log(data);
const dispose = emitter.subscribe(listener);
emitter.emit('Hello');
dispose();
const store = new DisposableStore();
store.add(emitter.subscribe(() => console.log('事件 1')));
store.add(emitter.subscribe(() => console.log('事件 2')));
store.add(emitter.subscribe(() => console.log('事件 3')));
store.dispose();
可用工具
| 类 | 目的 | 用例 |
|---|
Emitter<T> | 事件发射器 | 发布-订阅 |
DisposableStore | 管理一次性订阅 | 清理多个订阅 |
Disposable | 资源清理 | 追踪单个资源 |
模式 1:基础发射器
import { Emitter } from '@x-oasis/emitter';
const userEvents = new Emitter<{ id: string; action: string }>();
const unsubscribe = userEvents.subscribe((event) => {
console.log(`用户 ${event.id} 做了 ${event.action}`);
});
userEvents.emit({ id: '123', action: 'login' });
userEvents.emit({ id: '123', action: 'logout' });
unsubscribe();
模式 2:用于清理的 DisposableStore
import { Emitter } from '@x-oasis/emitter';
import { DisposableStore } from '@x-oasis/disposable';
class ChatComponent {
private disposables = new DisposableStore();
private messages = new Emitter<string>();
constructor() {
this.disposables.add(
this.messages.subscribe((msg) => {
this.displayMessage(msg);
})
);
this.disposables.add(
window.addEventListener('beforeunload', () => {
this.disposables.dispose();
})
);
}
sendMessage(text: string) {
this.messages.emit(text);
}
cleanup() {
this.disposables.dispose();
}
}
模式 3:事件链
import { Emitter } from '@x-oasis/emitter';
const input = new Emitter<string>();
const validated = new Emitter<string>();
const transformed = new Emitter<{ original: string; upper: string }>();
input.subscribe((value) => {
if (value.length > 0) {
validated.emit(value);
}
});
validated.subscribe((value) => {
transformed.emit({
original: value,
upper: value.toUpperCase(),
});
});
transformed.subscribe((result) => {
console.log('最终:', result);
});
input.emit('hello');
input.emit('');
模式 4:DOM 事件包装
import { Emitter, fromDomEvent } from '@x-oasis/emitter';
const clickEvent = fromDomEvent(button, 'click');
const scrollEvent = fromDomEvent(window, 'scroll');
clickEvent.subscribe(() => {
console.log('按钮被点击');
});
scrollEvent.subscribe((event) => {
console.log('滚动:', (event as any).target.scrollY);
});
模式 5:事件过滤
import { Emitter } from '@x-oasis/emitter';
class EventBus {
private emitter = new Emitter<{ type: string; payload: any }>();
on<T = any>(type: string, listener: (payload: T) => void) {
return this.emitter.subscribe((event) => {
if (event.type === type) {
listener(event.payload);
}
});
}
emit<T = any>(type: string, payload: T) {
this.emitter.emit({ type, payload });
}
}
const bus = new EventBus();
bus.on('user:login', (user) => {
console.log('登录:', user.name);
});
bus.on('user:logout', () => {
console.log('退出');
});
bus.emit('user:login', { name: 'John', id: 1 });
bus.emit('user:logout', {});
模式 6:一次性监听器
import { Emitter } from '@x-oasis/emitter';
class EventEmitter<T> {
private emitter = new Emitter<T>();
subscribe(listener: (event: T) => void) {
return this.emitter.subscribe(listener);
}
once(listener: (event: T) => void) {
const disposable = this.emitter.subscribe((event) => {
listener(event);
disposable();
});
return disposable;
}
emit(event: T) {
this.emitter.emit(event);
}
}
const emitter = new EventEmitter<string>();
emitter.once((msg) => {
console.log('第一条消息:', msg);
});
emitter.emit('Hello');
emitter.emit('Hello again');
模式 7:React 集成
import { useEffect, useState } from 'react';
import { Emitter } from '@x-oasis/emitter';
const notificationEmitter = new Emitter<{ type: string; message: string }>();
function useNotifications() {
const [notifications, setNotifications] = useState([]);
useEffect(() => {
const dispose = notificationEmitter.subscribe((notif) => {
setNotifications((prev) => [...prev, notif]);
setTimeout(() => {
setNotifications((prev) =>
prev.filter((n) => n !== notif)
);
}, 3000);
});
return dispose;
}, []);
return notifications;
}
function MyApp() {
const notifications = useNotifications();
return (
<div>
{notifications.map((notif, i) => (
<div key={i} className={`notif notif-${notif.type}`}>
{notif.message}
</div>
))}
<button
onClick={() =>
notificationEmitter.emit({
type: 'success',
message: '已保存!',
})
}
>
保存
</button>
</div>
);
}
模式 8:错误处理
import { Emitter } from '@x-oasis/emitter';
class SafeEmitter<T> {
private emitter = new Emitter<T>();
subscribe(
listener: (event: T) => void,
errorHandler?: (error: Error) => void
) {
return this.emitter.subscribe((event) => {
try {
listener(event);
} catch (error) {
if (errorHandler) {
errorHandler(error as Error);
} else {
console.error('事件监听器错误:', error);
}
}
});
}
emit(event: T) {
this.emitter.emit(event);
}
}
const emitter = new SafeEmitter<string>();
emitter.subscribe(
(msg) => {
throw new Error('哎呀');
},
(error) => {
console.error('捕获:', error.message);
}
);
emitter.emit('test');
最佳实践
✅ 做法
const dispose = emitter.subscribe(handler);
dispose();
const store = new DisposableStore();
store.add(emitter1.subscribe(handler1));
store.add(emitter2.subscribe(handler2));
store.dispose();
useEffect(() => {
const dispose = emitter.subscribe(handler);
return () => dispose();
}, []);
❌ 不做法
emitter.subscribe(handler);
listeners.push(emitter.subscribe(handler));
for (const item of items) {
emitter.subscribe(handler);
}
常见陷阱
陷阱 1:内存泄漏
useEffect(() => {
emitter.subscribe((event) => {
setState(event);
});
}, []);
useEffect(() => {
const dispose = emitter.subscribe((event) => {
setState(event);
});
return () => dispose();
}, []);
陷阱 2:卸载后订阅
useEffect(() => {
setTimeout(() => {
emitter.subscribe(handler);
}, 100);
}, []);
useEffect(() => {
const timeoutId = setTimeout(() => {
emitter.subscribe(handler);
}, 100);
return () => clearTimeout(timeoutId);
}, []);
陷阱 3:监听器上下文丢失
class Component {
private count = 0;
constructor() {
emitter.subscribe(this.handleEvent);
}
private handleEvent(event: any) {
this.count++;
}
}
class Component {
private count = 0;
constructor() {
emitter.subscribe(this.handleEvent.bind(this));
emitter.subscribe((event) => this.handleEvent(event));
}
private handleEvent(event: any) {
this.count++;
}
}
高级模式
高级 1:类型安全事件总线
import { Emitter } from '@x-oasis/emitter';
interface EventMap {
'user:login': { userId: string };
'user:logout': { userId: string };
'message:new': { text: string; from: string };
}
class TypedEventBus {
private emitters = new Map<string, Emitter<any>>();
on<K extends keyof EventMap>(
event: K,
listener: (payload: EventMap[K]) => void
) {
if (!this.emitters.has(event as string)) {
this.emitters.set(event as string, new Emitter<EventMap[K]>());
}
return this.emitters.get(event as string)!.subscribe(listener);
}
emit<K extends keyof EventMap>(event: K, payload: EventMap[K]) {
this.emitters.get(event as string)?.emit(payload);
}
}
const bus = new TypedEventBus();
bus.on('user:login', (payload) => {
console.log(payload.userId);
});
bus.emit('user:login', { userId: '123' });
高级 2:事件防抖
import { Emitter } from '@x-oasis/emitter';
import { debounce } from '@x-oasis/debounce';
class DebouncedEmitter<T> {
private emitter = new Emitter<T>();
private debouncedEmit = debounce((event: T) => {
this.emitter.emit(event);
}, 300);
subscribe(listener: (event: T) => void) {
return this.emitter.subscribe(listener);
}
emit(event: T) {
this.debouncedEmit(event);
}
}
const emitter = new DebouncedEmitter<string>();
emitter.subscribe((msg) => console.log('发出:', msg));
emitter.emit('fast');
emitter.emit('fast');
emitter.emit('fast');
参考资料