事件总线中回调参数的打字稿类型

Typescript types for arguments of a callback in an event bus

使用我们自制的事件总线,我们收到以下 TS 错误:

TS2345: Argument of type 'unknown' is not assignable to parameter of type 'AccountInfo | undefined'.
  Type 'unknown

事件总线使用类型 unknown[] 作为回调函数的参数。当然不可能在事件总线的参数中设置所有可能的类型。所以我们在如何让 TypeScript 正确推断参数类型或者是否有其他解决方案上有点卡住了?

// eventBus.ts
type TCallBack = (...args: unknown[]) => boolean | void

const subscriptions: { [key: string]: TCallBack[] } = {}

interface ISubscription {
  eventName: string
  callback: TCallBack
}

export function unsubscribe({ eventName, callback }: ISubscription) {
  if (!subscriptions[eventName]) { return }
  const index = subscriptions[eventName].findIndex((l) => l === callback)
  if (index < 0) { return }
  subscriptions[eventName].splice(index, 1)
}

export function subscribe({ eventName, callback }: ISubscription) {
  if (!subscriptions[eventName]) { subscriptions[eventName] = [] }
  subscriptions[eventName].push(callback)
  return () => unsubscribe({ eventName, callback })
}

export function publish(eventName: string, ...args: unknown[]) {
  if (!subscriptions[eventName]) { return }
  for (const callback of subscriptions[eventName]) {
    const result = callback(...args)
    if (result === false) { break }
  }
}

在应用程序的某个时刻,我们发布 login 事件来触发所有订阅者:

// authServce.ts
publish('login', account)

之后触发订阅者:

// authStore.ts
export const setAccount = (account?: AuthenticationResult['account']) => {
  if (account) state.account = account
  else state.account = defaultState().account
  console.log('setAccount: ', state.account)
}

subscribe({
  eventName: 'login',
  callback: (account) => {
    setAccount(account)
  },
})

此代码可以完美运行,但如果能够解决 TS 错误就更好了。

扩展any并将默认设置为any(这样你就不需要每次都指定类型)

// eventBus.ts
type TCallBack<T extends any = any> = (...args: T[]) => boolean | void

const subscriptions: { [key: string]: TCallBack[] } = {}

interface ISubscription<T> {
  eventName: string
  callback: TCallBack<T>
}

export function unsubscribe<T>({ eventName, callback }: ISubscription<T>) {
  if (!subscriptions[eventName]) { return }
  const index = subscriptions[eventName].findIndex((l) => l === callback)
  if (index < 0) { return }
  subscriptions[eventName].splice(index, 1)
}

export function subscribe<T>({ eventName, callback }: ISubscription<T>) {
  if (!subscriptions[eventName]) { subscriptions[eventName] = [] }
  subscriptions[eventName].push(callback)
  return () => unsubscribe({ eventName, callback })
}

export function publish<T>(eventName: string, ...args: T[]) {
  if (!subscriptions[eventName]) { return }
  for (const callback of subscriptions[eventName]) {
    const result = callback(...args)
    if (result === false) { break }
  }
}