首页 > 解决方案 > CallableFunction.call 中的“extends keyof”变为“never”

问题描述

下面方法的第二个参数(loadingName)的类型是第一个参数的key。

(alias) function withLoading<T, P extends keyof T>(this: T, loadingName: P, before: () => Promise<any>): Promise<void>
import withLoading

但是,当我调用时withLoading.call,类型loadingName变为never

(method) CallableFunction.call<this, [loadingName: never, before: () => Promise<any>], Promise<void>>(this: (this: this, loadingName: never, before: () => Promise<any>) => Promise<...>, thisArg: this, loadingName: never, before: () => Promise<any>): Promise<...>

标签: typescript

解决方案


为了将其简化为最小的可重现示例,假设您具有以下功能func

declare function func<T>(this: T, key: keyof T): void;

您希望能够按如下方式调用它,但它不起作用:

func.call({ a: 123 }, "a") // error?!
// CallableFunction.call<{ a: number; }, [key: never], void>(...);

为什么?


您正在组合两个泛型函数,并希望编译器可以推断它们之间的高阶关系,以便生成的行为将泛型类型参数从一个函数传播到另一个函数,而无需先指定它们。

这并不是一件完全疯狂的事情,因为 TypeScript 3.4 引入了对泛型函数的高阶类型推断的一些支持,而 TypeScript 3.5 引入了对泛型构造函数的类似支持

事实上,如果你有一个call()没有this参数的独立函数,事情就会如愿以偿:

declare function call<T, A extends any[], R>(
  thisArg: T, cb: (this: T, ...args: A) => R, ...args: A): R;
call({ a: 123 }, func, "a") // okay
call({ a: 123 }, func, "b") // error

那么为什么它不能与this参数一起使用呢?


简短但可能不令人满意的答案是,对高阶函数推理的支持本质上是启发式的,并且在未明确实现的情况下不起作用。如microsoft/TypeScript#30215中所述,实现此支持的拉取请求:

上述算法不是一个完整的统一算法,也不是完美的。

虽然如microsoft/TypeScript#30134中所述的完全统一可能会有所帮助,但它需要对当前在 TypeScript 中的类型推断方式进行相当大的改变,并且可能在编译器性能方面存在问题。无论如何,就目前而言,我们有一个不完美但性能良好的算法。


在研究这个时,我注意到microsoft/TypeScript#33139的存在,这是一个声称使这种推理适用于this参数的拉取请求。我自己没有测试过,所以我不能确定它是否符合它的要求。虽然它似乎已分配给首席语言架构师,但它已经坐了很长时间没有改变。通常拉取请求应该链接到特定的错误报告或功能请求问题,但我在这里看不到。


那么可以做些什么呢?直接的解决方法是自己手动指定泛型类型参数,如下所示:

func.call<{ a: number }, [key: keyof { a: number }], void>({ a: 123 }, "a"); // okay

不得不这样做并不是很好,但至少编译器可以认识到这种类型参数规范适用于func.call......这绝对比没有好。换句话说,您不必在这里求助于不安全的类型断言

从长远来看,您可能想对microsoft/TypeScript#30215发表评论,说明为什么完整的统一算法会改进事情。有人可能还会考虑提交单独的功能请求,以扩展 TypeScript 3.4 对高阶函数的支持以包含this参数,并在其中链接到microsoft/TypeScript#33139。这些步骤都不能保证对事情何时或是否会改变有任何影响,但它可能不会受到伤害。

Playground 代码链接


推荐阅读