具有通用返回类型的打字稿函数

Posted

技术标签:

【中文标题】具有通用返回类型的打字稿函数【英文标题】:Typescript Function with Generic Return Type 【发布时间】:2020-05-27 10:06:18 【问题描述】:
type FuncGenericReturn = <T>() => T;
const funcReturnsNumber: FuncGenericReturn = (): number => 1;

(Sandbox)

收到此错误:

类型“数字”不能分配给类型“T”。 “数字”可分配给“T”类型的约束,但“T”可以用约束“”的不同子类型实例化。(2322) input.ts(1, 26):预期类型来自此签名的返回类型。

我希望打字稿自动将 T 推断为数字并使用它。它为什么抱怨?写这样的东西的正确方法是什么?谢谢。

【问题讨论】:

【参考方案1】:

重要的是要注意泛型类型参数的声明位置以及它们的作用域。类型

type FuncGenericReturn = <T>() => T;

是一个具体类型,指的是一个泛型函数&lt;T&gt;() =&gt; T 的意思是:“一个函数,其调用者 指定了一个T 类型并返回一个T 类型的值。”这基本上是不可能安全实施的。想象一下,如果你有这样的功能:

declare const funcGenericReturn: FuncGenericReturn;

那么你应该可以这样称呼它:

const someNumber: number = funcGenericReturn<number>(); 
const someString: string = funcGenericReturn<string>();

当然,在运行时它们都将编译为

const someNumber = funcGenericReturn();
const someString = funcGenericReturn();

这意味着funcGenericReturn() 只需要在运行时“知道”它应该首先返回一个number,然后是一个string,基于在生成javascript 之前的erased 类型信息。所以正确实现FuncGenericReturn 需要神奇的预知。

重申:当您有一个泛型函数时,泛型类型参数由调用者指定,而不是由实现者指定。确实,有时编译器会推断这些类型参数,以便编写代码的人不必将其拼写出来,但同样,这些推断发生在调用时。对同一个泛型函数的两次不同调用最终可能对类型参数有两种不同的选择。


让我们将其与一个不同但相关的类型定义进行比较:

type FuncConcreteReturn<T> = () => T;

这里,FuncConcreteReturn 是一个泛型,指的是一个具体函数。更准确地说,FuncConcreteReturn 不是真正的类型;它更像是一个类型运算符,它接受一个输入类型T 并产生一个输出类型() =&gt; T

对于任何特定类型TFuncConcreteReturn&lt;T&gt; 类型是一个具体 函数类型,它不接受参数并返回T 类型的值。所以FuncConcreteReturn&lt;string&gt; 是一个不带参数并返回string 的函数,而FuncConcreteReturn&lt;number&gt; 是一个不带参数并返回number 的函数。请注意,FuncConcreteReturn&lt;string&gt;FuncContreteReturn&lt;number&gt; 是不同的类型,它们都不是 FuncConcreteReturn,因为这不是有效类型。所以以下是有效的:

const funcReturnsNumber: FuncConcreteReturn<number> = () => 1;
const funcReturnsString: FuncConcreteReturn<string> = () => "";

同样,funcReturnsNumber 不是一个通用函数。它是一个始终返回数字的具体函数。而FuncConcreteReturn&lt;T&gt; 是一个通用类型,其中T 的值是在写出类型时选择的。由于这些类型是函数类型,T 类型由这些函数的实现者选择,而不是由调用者选择。


顺便说一下泛型函数类型之类的关系

type G = <T, U>(t: T, u: U) => [T, U]

还有一个泛型类型

type H<T, U> = (t: T, u: U) => [T, U]

是后者的任何实例都将是前者的实例,但反之则不然。这意味着如果您确实拥有FuncGenericReturn,您可以将其分配给FuncConcreteReturn&lt;string&gt;FuncConcreteReturn&lt;number&gt; 类型的值:

const fn: FuncConcreteReturn<number> = funcGenericReturn; // okay
const fs: FuncConcreteReturn<string> = funcGenericReturn; // okay

或者,对于上面的 GH 类型,您可以这样做:

const g: G = <T, U>(t: T, u: U) => [t, u];
g("a", 1); // okay
g(1, "a"); // okay

const h1: H<string, number> = g; // okay
h1("a", 1); // okay
h1(1, "a"); // error

const h2: H<number, string> = g; // okay
h2(1, "a"); // okay
h2("a", 1); // error

好的,我希望这能让您对泛型函数和泛型类型之间的区别有所了解。祝你好运!

Playground link to code

【讨论】:

【参考方案2】:

这个语法不适合你吗?

type FuncGenericReturn<T> = () => T;
const funcReturnsNumber: FuncGenericReturn<number> = () => 1;

【讨论】:

问题是问为什么 TS 不自动推断类型。当然,如果您明确地将类型分配给泛型,它将起作用。

以上是关于具有通用返回类型的打字稿函数的主要内容,如果未能解决你的问题,请参考以下文章

通用无状态组件 React 的类型?或在打字稿中扩展通用函数接口以具有进一步的通用性?

打字稿中具有通用键的对象

打字稿:强制默认通用类型为“any”而不是“”

具有默认参数值的打字稿条件返回类型

打字稿不推断通用对象的值

接受通用参数和字符串数组的打字稿函数