useEventEmitter
作用
在多个组件之间进行事件通知有时会让人非常头疼,借助 EventEmitter
,可以让这一过程变得更加简单。
在组件中调用 useEventEmitter
可以获得一个 EventEmitter
的实例:
ts
const event$ = useEventEmitter()
在组件多次渲染时,每次渲染调用
useEventEmitter
得到的返回值会保持不变,不会重复创建EventEmitter
的实例。
通过 props
或者 Context
,可以将 event$
共享给其他组件。然后在其他组件中,可以调用 EventEmitter
的 emit
方法,推送一个事件,或是调用 useSubscription
方法,订阅事件。
ts
event$.emit('hello')
event$.useSubscription((val) => {
console.log(val)
})
useSubscription
会在组件创建时自动注册订阅,并在组件销毁时自动取消订阅。
对于子组件通知父组件的情况,我们仍然推荐直接使用 props
传递一个 onEvent
函数。而对于父组件通知子组件的情况,可以使用 forwardRef
获取子组件的 ref
,再进行子组件的方法调用。 useEventEmitter
适合的是在距离较远的组件之间进行事件通知,或是在多个组件之间共享事件通知。
原理
发布订阅设计模式实现
源码
ts
import { useRef, useEffect } from 'react'
type Subscription<T> = (val: T) => void
export class EventEmitter<T> {
// 保存所有的订阅
private subscriptions = new Set<Subscription<T>>()
// 发布
emit = (val: T) => {
// 遍历所有的订阅,执行回调
for (const subscription of this.subscriptions) {
subscription(val)
}
}
// 订阅
useSubscription = (callback: Subscription<T>) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
// 保存回调
const callbackRef = useRef<Subscription<T>>()
// 更新最新的回调
callbackRef.current = callback
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
// 定义订阅函数
function subscription(val: T) {
if (callbackRef.current) {
callbackRef.current(val)
}
}
// 添加订阅
this.subscriptions.add(subscription)
return () => {
// 取消订阅
this.subscriptions.delete(subscription)
}
}, [])
}
}
export default function useEventEmitter<T = void>() {
const ref = useRef<EventEmitter<T>>()
if (!ref.current) {
ref.current = new EventEmitter()
}
return ref.current
}