You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自制Event Bus的TypeScript回调参数类型错误解决方案求助

解决Event Bus的TypeScript类型推断问题

这个问题的核心是你的Event Bus目前用unknown[]作为回调参数类型,导致TypeScript无法推断具体事件对应的参数类型,从而抛出TS2345错误。我们可以通过泛型+事件类型映射的方式来实现类型安全的Event Bus,让TS能正确推断每个事件的参数类型。

具体解决方案

1. 定义事件类型映射

首先创建一个接口,用来映射每个事件名对应的参数类型数组。这样每个事件的参数类型就有了明确的定义:

// 先导入你的AccountInfo类型,替换成实际的类型导入路径
import type { AccountInfo } from './your-types';

// 事件类型映射:键是事件名,值是该事件的参数类型数组
type EventMap = {
  // login事件的参数是一个AccountInfo | undefined类型的参数
  login: [AccountInfo | undefined];
  // 可以在这里添加其他事件,比如:
  // logout: []; // logout事件没有参数
  // userUpdated: [UserInfo]; // userUpdated事件带一个UserInfo参数
};

2. 改造Event Bus的类型定义

接下来修改eventBus.ts里的类型和函数,用泛型约束来关联事件名和对应的参数类型:

import type { AccountInfo } from './your-types';

type EventMap = {
  login: [AccountInfo | undefined];
};

// 基于EventMap定义每个事件对应的回调类型
type EventCallback<E extends keyof EventMap> = (...args: EventMap[E]) => boolean | void;

// 订阅者的类型也改为对应EventMap的结构
const subscriptions: { [E in keyof EventMap]?: EventCallback<E>[] } = {};

interface ISubscription<E extends keyof EventMap> {
  eventName: E;
  callback: EventCallback<E>;
}

export function unsubscribe<E extends keyof EventMap>({ eventName, callback }: ISubscription<E>) {
  const eventSubscriptions = subscriptions[eventName];
  if (!eventSubscriptions) return;
  
  const index = eventSubscriptions.findIndex((cb) => cb === callback);
  if (index >= 0) {
    eventSubscriptions.splice(index, 1);
  }
}

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

export function publish<E extends keyof EventMap>(eventName: E, ...args: EventMap[E]) {
  const eventSubscriptions = subscriptions[eventName];
  if (!eventSubscriptions) return;
  
  for (const callback of eventSubscriptions) {
    const result = callback(...args);
    if (result === false) break;
  }
}

3. 使用改造后的Event Bus

现在在你的业务代码里使用时,TypeScript会自动推断参数类型,不会再报错:

// authServce.ts
publish('login', account); // TS会自动检查account是否符合AccountInfo | undefined类型

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

subscribe({
  eventName: 'login',
  callback: (account) => {
    // 这里account会被自动推断为AccountInfo | undefined
    setAccount(account); // 不再有TS类型错误
  },
});

为什么这个方案有效?

  • 我们通过EventMap明确了每个事件对应的参数类型,让TypeScript有了类型推断的依据。
  • 泛型E extends keyof EventMap确保了事件名只能是EventMap中定义的键,避免了拼写错误。
  • 回调函数的参数类型直接绑定到EventMap[E],所以TS能准确推断出每个事件的参数类型,保证了类型安全。

可选:支持动态事件(不推荐)

如果你确实不想预先定义所有事件,可以用泛型让调用者在使用时指定参数类型,但这样会损失一部分类型安全性:

// 修改后的publish和subscribe函数
export function subscribe<T extends unknown[]>(eventName: string, callback: (...args: T) => boolean | void) {
  if (!subscriptions[eventName]) {
    subscriptions[eventName] = [];
  }
  subscriptions[eventName].push(callback as TCallBack);
  
  return () => unsubscribe({ eventName, callback as TCallBack });
}

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

// 使用时指定类型
subscribe<[AccountInfo | undefined]>('login', (account) => {
  setAccount(account); // 这里account会被推断为AccountInfo | undefined
});

不过还是推荐第一种方案,因为它能提供更严格的类型检查,避免潜在的类型错误。

内容的提问来源于stack exchange,提问作者DarkLite1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 08:57:31