通过泛型进行函数参数类型推断的行为很奇怪

时间:2018-09-17 12:28:03

标签: typescript

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

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中的函数类型只能推断出最多的参数,并且只有它是唯一具有该数量的参数的类型。

1 个答案:

答案 0 :(得分: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