关于typescript:传递给_.debounce之类的通用函数包装器的函数的类型推断

Type inference for functions passed to generic function wrappers like _.debounce

在TypeScript中,如果将函数表达式作为参数传递,则可以完美地推断出其参数的类型:

1
2
3
4
5
6
var foo = (fn: (a: string) => void) => {};

foo(a => {
    // a is inferred to be a string
    alert(a.toLowerCase());
});

对于事件处理程序和其他回调而言,这确实很方便。 但是,如果函数表达式包装在通用包装函数的调用中,则该通用包装函数将函数作为参数并返回具有相同签名的函数(返回值除外),例如 Lodash的_.debounce,推理不会发生。

1
2
3
4
5
6
7
8
9
var debounce = < T >(fn: (a: T) => void) => {
    return (a: T) => { /* ... */ };
};

foo(debounce(a => {
    // a is inferred to be {}
    // type error: 'toLowerCase' doesn't exist on '{}'
    alert(a.toLowerCase());
}));

TS游乐场

由于foo希望其参数为(a: string) => void,因此编译器可以尝试为T找到这样的类型,即debounce< T >将返回(a: string) => void。 但是它不会尝试这样做。

我想念什么吗? 是否应该以其他方式编写debounce的类型注释? 是设计使然吗? GitHub上是否存在与此案有关的问题?

UPDATE-2017:现在可以使用! TS 2.5。


首先,当您调用不带类型参数的泛型函数时,编译器必须找出每个类型参数的类型。

因此,当您调用debounce时,编译器必须找到T类型的候选对象。但是debounce唯一可以从中得出推论的地方是您的函数,并且没有为type参数提供显式类型。

因此,编译器认为它没有任何类型可以使用,对于T,它退回到{}("空类型",通常称为"卷曲")。

整个过程称为类型参数推断。

然后发生的是,编译器会注意到您没有为箭头函数的参数指定类型。它认为可以从debounce的参数类型中找出来,而不是默认为any。此过程称为上下文类型化。

fn的类型基本上是(a: {}) => void,因此编译器认为"好吧,我们可以给a一个类型!"不幸的是最终变成了{}

因此,尽管这并非绝对出色,但修复的确不是那么糟糕。只需为a添加类型注释:

1
2
3
4
foo(debounce((a: string) => {
    // Everything is fine!
    alert(a.toLowerCase());
}));

或使用类型参数:

1
2
3
4
foo(debounce<string>(a => {
    // Everything is fine!
    alert(a.toLowerCase());
}));