代码之家  ›  专栏  ›  技术社区  ›  Neonit Humam Helfawi

通过泛型进行的函数参数类型推理行为怪异

  •  1
  • Neonit Humam Helfawi  · 技术社区  · 6 年前

    请看下面这个简单的示例代码:

    interface EventEmitter<ListenersT>
    {
        on<EventT extends keyof ListenersT>(event: EventT, listener: ListenersT[EventT]): this;
    }
    
    interface MyEvents
    {
        foo(x: number): void;
        bar(): void;
        moo(a: string, b: Date): void;
    }
    
    interface MyEmitter extends EventEmitter<MyEvents>
    {}
    
    const my: MyEmitter = <any> {};
    
    my.on('foo', (x) => x / 4); // Parameter 'x' implicitly has an 'any' type.
    my.on('bar', () => 42);
    my.on('moo', (a, b) => a + ': ' + b.getFullYear());
    

    我想知道为什么 my.on('foo', ...) x ,而对于 my.on('moo', ...)

    你可以去测试一下 on the Typescript Playground . 您可以启用 noImplicitAny 按下 选项 按钮以获取我在评论中输入的警告或将鼠标悬停在 number

    额外的好处:如果我再加上一个参数,这就更奇怪了 foo() . 在这种情况下,它也无法推断 moo 的论点。

    编辑1

    它似乎只能推断中函数的类型 MyEvents 只有当它是唯一一个有那么多争论的时候。

    1 回复  |  直到 6 年前
        1
  •  1
  •   Titian Cernicova-Dragomir    6 年前

    当回调的类型依赖于另一个参数时,Typescript很难推断回调的参数,我在几个SO问题中看到了这一点(如果您愿意,我可以搜索一些)。

    解决办法是用另一种方式来解决这个问题,那就是使 on 函数有几个重载。诀窍是这样做,而不必重述接口中的所有签名。

    您的要求与 this 答案,您将在那里找到更详细的解释,但是根据您的需要修改代码,代码将如下所示:

    interface EventEmitter<ListenersT>
    {
        on : OnAll<ListenersT, this>
    }
    
    type UnionToIntersection<U> =
        (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
    
    type OnSignatures<T, TReturn> = { [P in keyof T]: (event: P, listener: T[P]) => TReturn }
    type OnAll<T, TReturn> = UnionToIntersection<OnSignatures<T, TReturn>[keyof T]>
    
    interface MyEvents
    {
        foo(x: number): void;
        bar(): void;
        moo(a: string, b: Date): void;
    }
    
    interface MyEmitter extends EventEmitter<MyEvents>
    {
        // on is now equivalent to an on with these overloads
        //on(event: 'foo', listener: (x: number) => void): this;
        //on(event: 'bar', listener: () => void): this;
        //on(event: 'moo', listener: (a: string, b: Date)=> void): this;
    }
    
    const my: MyEmitter = <any> {};
    
    my.on('foo', (x) => x / 4); //  x is number 
    my.on('bar', () => 42);
    my.on('moo', (a, b) => a + ': ' + b.getFullYear());
    

    Playground link