Typescript 基于事件键的事件回调类型缩小

Typescript 基于事件键的事件回调类型缩小,typescript,Typescript,在搜索如何编写类型安全事件发射器的选项时,我偶然发现了这样一种模式:首先在接口中定义事件名称及其值,例如: interface UserEvents { nameChanged: string; phoneChanged: number; } 而上的“方法被定义为获取一个keyof UserEvents,以及一个回调,其参数的类型为使用称为lookup type-UserEvents[keyof T]的功能获得的键所指向的类型: function on<T extends

在搜索如何编写类型安全事件发射器的选项时,我偶然发现了这样一种模式:首先在接口中定义事件名称及其值,例如:

interface UserEvents {
    nameChanged: string;
    phoneChanged: number;
}
而上的“方法被定义为获取一个keyof UserEvents,以及一个回调,其参数的类型为使用称为lookup type-UserEvents[keyof T]的功能获得的键所指向的类型:

function on<T extends keyof UserEvents>(e: T, cb: (val: UserEvents[T]) => void) {/**/}
我遇到的问题是在“on”方法的主体中,我无法根据传递的键值缩小传递回调的类型。由于事件名称及其类型是在同一个接口中定义的,如果我检查了键类型,我希望回调会缩小,但情况并非如此:

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
}
函数on(e:T,cb:(:UserEvents[T])=>void){ 如果(e==='nameChanged'){ //期望cb类型为(字符串)=>void }否则{ //这里是(数字)=>void } //但不管检查结果如何,cb类型 //方法主体内部的类型为(字符串和数字)=>void }
是否有任何方法可以使用其他类型脚本功能(如类型保护、区分联合等)实现基于事件键的回调的自动类型推断,并使用此特定方法调用签名?

为了实现想要的行为,我们需要将这两种功能组合到一个数据结构中,幸运的是,当选择这种结构并将其与扩展函数参数相结合时,结果将符合确切的需要

考虑以下代码:

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

第二件重要的事情是注释函数参数
…args:KeyWithCallback
。从现在起,函数体将理解函数有两个参数,一个参数与另一个参数相关,并且在union类型的每一对中都描述了这种关系


检查对中的第一个元素会自动推断第二个元素

我也有类似的问题。。。但是,实现仍然会抱怨,因为TypeScript类型保护只会缩小单个值的类型。也就是说,在if(a){}else{},在then和else子句中,a的类型可能会缩小,但在检查a时,b的类型不会缩小,即使a和b的类型之间存在一些约束。”。检查此Ach,我们可以通过KeyWithCallback[keyof A]做得更好。将用第三个解决方案更新我的答案!我从未想过将事件与其在元组中的回调绑定在一起,并更改方法的定义以获取一个rest参数,该参数的类型是这些元组的并集,并且VsCode在调用该方法时和对于if都提供了预期的智能感知checks@Codeceps我清理了答案,也不需要使用手动接头。因此,我们从KeyWithCallback中获得了开箱即用的联合
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

| ["nameChanged", (_: string) => void] 
| ["phoneChanged", (_: number) => void]