函数参数类型通过泛型的推理表现得很奇怪

问题描述 投票:1回答:1

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

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标志来获取我在注释中添加的警告,或者将鼠标悬停在代码窗口中的x参数上,以查看它无法推断number类型。

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

编辑1

它似乎只能用MyEvents推断出具有最多参数的函数的类型,并且只有它是唯一具有该数量的参数的函数。

typescript
1个回答
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

© www.soinside.com 2019 - 2024. All rights reserved.