Skip to content

useEventEmitter

作用

在多个组件之间进行事件通知有时会让人非常头疼,借助 EventEmitter ,可以让这一过程变得更加简单。

在组件中调用 useEventEmitter 可以获得一个 EventEmitter 的实例:

ts
const event$ = useEventEmitter()

在组件多次渲染时,每次渲染调用 useEventEmitter 得到的返回值会保持不变,不会重复创建 EventEmitter 的实例。

通过 props 或者 Context ,可以将 event$ 共享给其他组件。然后在其他组件中,可以调用 EventEmitteremit 方法,推送一个事件,或是调用 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
}

如有转载或 CV 的请标注本站原文地址