eventbus:使用发布订阅模式,多用于跨组件跨页面通信,即将一些个互不相干的远房亲戚建立通信。并不需要改动过多的代码,只需在需要的时候注册监听即可,大大降低代码耦合。
eventbus 的应用场景
此时有这么一个需求,实现一个类似微信小程序的 toast ,使其在任意页面可直接调用显示,此时就可以用 eventbus 将 toast 与任意页面建立联系。
// 主入口
<>
<Toast />
<Routes />
</>
// toast
const Toast = () => {
const [toasts, setToasts] = useState([])
useEffect(() => {
const addToast = ({ type = 'success', title, message, duration = 5 }) => {
const id = uuid()
setToasts((currentToasts) => [
{ id, type, title, message },
...currentToasts
])
if (duration) {
setTimeout(() => removeToast(id), duration * 1000)
}
}
const removeToast = (id) => {
setToasts((currentToasts) =>
currentToasts.filter((toast) => toast.id !== id)
)
}
// 添加订阅
eb.on('toast', addToast)
return () => eb.off('toast', addToast)
}, [])
return (
<div>
<TransitionGroup>
{toasts.map((toast) => (
<CSSTransition key={toast.id} classNames="toast" timeout={200}>
<div
key={toast.id}
type={toast.type}
onClick={() => removeToast(toast.id)}
>
{toast.title && <Title>{toast.title}</Title>}
{toast.message && <Message>{toast.message}</Message>}
</div>
</CSSTransition>
))}
</TransitionGroup>
</div>
)
}
// 任意位置
eb.emit('toast', { title: 'Hello', message: 'Hello' })
简易 eventbus 实现
class EventBus {
private events: Record<string, Array<any>> = Object.create(null)
on<T>(name: string, fn: (data: T) => void) {
this.events[name] || (this.events[name] = [])
this.events[name].push(fn)
}
emit(name: string, data?: any) {
this.events[name]?.forEach((fn: Function) => {
fn(data)
})
}
once<T>(name: string, fn: (data: T) => void) {
this.events[name] || (this.events[name] = [])
const f = (...arg: any) => {
fn(arg)
this.off(name, f)
}
this.events[name].push(f)
}
off(name: string, fn?: Function) {
if (fn) {
this.events[name].splice(
this.events[name].findIndex((_) => _ === fn),
1
)
} else {
delete this.events[name]
}
}
}
基于 DOM 的实现
class DomEventBus {
private eventTarget: EventTarget
constructor(comment: string) {
this.eventTarget = document.createComment(comment)
}
on<T>(name: string, listener: (event: CustomEvent<T>) => void) {
this.eventTarget.addEventListener(name, listener as any)
}
once<T>(name: string, listener: (event: CustomEvent<T>) => void) {
this.eventTarget.addEventListener(name, listener as any, { once: true })
}
emit<T>(name: string, data?: T) {
this.eventTarget.dispatchEvent(new CustomEvent(name, { detail: data }))
}
off(name: string, listener: (event: CustomEvent<any>) => void) {
this.eventTarget.removeEventListener(name, listener as any)
}
}