【问题标题】:Function Argument Type Inference through Generics behaves weirdly通过泛型的函数参数类型推断行为怪异
【发布时间】:2019-02-21 09:06:03
【问题描述】:

看看这个简单的示例代码:

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。您可以通过按 Options 按钮来启用 noImplicitAny 标志,以获取我在评论中放置的警告,或者将鼠标悬停在代码窗口中的 x 参数上,以查看它未能推断出number 类型。

奖励:如果我向 foo() 添加另一个参数,这会变得更加奇怪。在这种情况下,它也无法推断 moo 的参数的类型。

编辑 1

它似乎只能推断 MyEvents 中具有最多参数的函数的类型,并且只有当它是唯一具有这么多参数的函数时。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    当回调的类型取决于另一个参数时,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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-01
      • 2020-03-12
      • 1970-01-01
      • 1970-01-01
      • 2016-12-05
      • 1970-01-01
      • 1970-01-01
      • 2020-03-22
      相关资源
      最近更新 更多