自制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
相关产品推荐
相关产品推荐

