传递给通用函数包装器(如 _.debounce)的函数的类型推断

Posted

技术标签:

【中文标题】传递给通用函数包装器(如 _.debounce)的函数的类型推断【英文标题】:Type inference for functions passed to generic function wrappers like _.debounce 【发布时间】:2016-06-07 09:49:42 【问题描述】:

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

var foo = (fn: (a: string) => void) => ;

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

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

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 Playground

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

我错过了什么吗?我是否应该以其他方式为debounce 编写类型注释?是设计使然吗? GitHub上有关于这个案例的问题吗?

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

【问题讨论】:

我认为这只是类型推断系统的限制。它不能仅基于作为参数接收的函数的参数来推断泛型函数的类型。我在这里进一步探讨了这个问题:***.com/questions/34531390/… 不要认为这是同一个问题。没想到它会从参数中推断出泛型函数的类型。我预计推断会发生在相反的方向,从 AST 的根到它的叶子。 【参考方案1】:

首先,当你调用一个没有类型参数的泛型函数时,编译器必须找出每个类型参数的类型。

所以当你调用debounce 时,编译器必须找到T 类型的候选者。但是debounce 唯一可以从中得出推论的地方是您的函数,而且您没有为您的类型参数提供明确的类型。

因此编译器认为它没有任何类型可以使用,并回退到(“空类型”,通常称为“curly curly”)以获取T

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

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

好吧,fn 的类型基本上是 (a: ) =&gt; void,所以编译器认为“好吧,太好了,我们可以给 a 一个类型!”不幸的是,这最终是...


因此,虽然这不是绝对很棒,但修复并没有那么糟糕。只需为a添加类型注释:

foo(debounce((a: string) =>  
    // Everything is fine!
    alert(a.toLowerCase());
));

或使用类型参数:

foo(debounce<string>(a =>  
    // Everything is fine!
    alert(a.toLowerCase());
));

【讨论】:

至少我们得到,而不是any,所以这种情况不会被忽视。尽管如此,T 的类型是从a 推断出来的,反之亦然,这有点奇怪。 “debounce 唯一可以从中得出推论的地方是你的函数……”它也可以尝试从foo 的参数类型中得出它们。那是我的问题。我想确保 TS 不会这样做。谢谢你的澄清。

以上是关于传递给通用函数包装器(如 _.debounce)的函数的类型推断的主要内容,如果未能解决你的问题,请参考以下文章

JNI 回调传递给 C

Gutenberg/React 将动态属性传递给过滤函数

将 debouncer 与 React 事件一起使用

用于句柄的 Win API 包装类

如何用装饰器包装 func.__code__.co_filename?

为具有可变数量参数的不同函数编写通用包装器