在搜索有关如何编写类型安全事件发射器的选项时,我偶然发现了这种模式,您首先在接口(interface)中定义事件名称及其值,例如:
interface UserEvents {
nameChanged: string;
phoneChanged: number;
}
并且“on”方法被定义为采用keyof UserEvents,以及一个回调,其参数是由使用称为查找类型的功能获取的 key - UserEvents[keyof T]:
function on<T extends keyof UserEvents>(e: T, cb: (val: UserEvents[T]) => void) {/**/}
提供类型安全的方法调用:
on('nameChanged', (val) => {}); // callback parameter type inferred to be a string
on('phoneChanged', (val) => {}); // ... inferred as number
我遇到的问题是在“on”方法的主体内部,我无法根据传递的键值缩小传递的回调的类型。由于事件名称及其类型是在同一接口(interface)中定义的,因此如果我检查 key 类型,我预计回调会缩小,但事实并非如此:
function on<T extends keyof UserEvents>(e: T, cb: (_: UserEvents[T]) => void) {
if (e === 'nameChanged') {
// expecting the cb type to be (string) => void
} else {
// and here to be (number) => void
}
// but regardless of the checks, the cb type
// inside the method body is of type (string & number) => void
}
是否有任何方法可以使用其他 TypeScript 功能(例如类型保护、可区分联合等)以及此特定方法调用签名来实现基于事件键的回调的自动类型推断?
最佳答案
为了实现想要的行为,我们需要将两者组合成一个数据结构,幸运的是当选择 tuple 时对于这样的结构并将其与扩展函数参数相结合,结果将满足确切的需求。
考虑下面的代码:
interface UserEvents {
nameChanged: string;
phoneChanged: number;
}
// generic type in order to use with different objects then UserEvents only
type KeyWithCallback<A extends object> = {
[K in keyof A]: [K, (_: A[K]) => void]
}[keyof A];
function on(...args: KeyWithCallback<UserEvents>) {
if (args[0] === 'nameChanged') {
const [_, clb] = args; // destructuring inside condition
clb('') // here clb allows on string only (string) => void
} else {
const [_, clb] = args; // destructuring inside condition
clb(1); // here clb allows on number only (number) => void
}
}
// using
on('nameChanged', (a:string) => {}) // ok
on('nameChanged', (a:number) => {}) // error as expected
我用过mapped type KeyWithCallback
将所有可能的参数表示为 [key, callback]
对。结果是对的并集(第二个元组)。
KeyWithCallback<UserEvents>
评估为:
| ["nameChanged", (_: string) => void]
| ["phoneChanged", (_: number) => void]
第二件重要的事情是注释函数参数 ...args: KeyWithCallback<UserEvents>
。从现在开始,函数体将理解函数有两个参数,一个参数与另一个参数相关,并且这种关系在联合类型的每一对中进行描述。
检查该对的第一个元素会自动推断第二个元素。
关于typescript - 根据事件键缩小事件回调的类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59174764/