为什么Typescript需要“infer”关键字?

144

为什么Typescript的开发者们创造了infer关键字? 根据文档,以下是使用它的示例:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

我不明白为什么这是必要的。为什么不能只是这样:

type ReturnType<T> = T extends (...args: any[]) => R ? R : any;

为什么这段代码不起作用?为什么需要使用 "infer" 关键字?
4个回答

129
使用infer,编译器确保您已明确声明了所有类型变量。
type MyType<T> = T extends infer R ? R : never;
type T1 = MyType<{b: string}> // T1 is { b: string; }

在这里,我们在`MyType`中声明了一个`R`的类型变量,它是从`T`中推断出来的。 (注意,在条件类型的`extends`子句中才使用`infer`。
现在,使用未声明的类型参数可能会导致编译错误:
type MyType2<T> = T extends R2 ? R2 : never; // error, R2 undeclared

没有infer,编译器就无法确定你是否想引入一个额外的类型变量R2来进行推断(参见第一个情况),还是R2只是一个意外的打字错误。infer的存在就是为了消除这种歧义。
更准确地说,当省略infer时,编译器会检查T是否可分配给R
type R = { a: number }
type MyType3<T> = T extends R ? R : never; // compare T with type R
type T3 = MyType3<{b: string}> // T3 is never

请注意,infer R会覆盖同名类型声明R的类型引用。
type R = { a: number }
type MyType4<T> = T extends infer R ? R : never;
type T4 = MyType4<{b: string}> // { b: string; }

游乐场


我认为MyType2应该可以工作。T extends R应该意味着任何扩展我们上面定义的R的类型。你所说的“将T与上面的类型R进行比较”是什么意思? - CodyBugstein
2
type MyType2<T> = ... 是一个类型别名声明,其中泛型类型变量/参数 T 无法被解析。稍后我们通过在类型别名声明 type T2 = ... 中使用类型引用 MyType2<{b: string}> 来实例化 T{b: string}。现在,实际类型可以被解析了。由于 {b: string}T 的实例)不能被分配(这是“与之比较”的更具体表达方式)给 R - 它是 {a: number} -,因此 T2 解析为 never。希望这样能让事情更清楚。 - ford04
通用类型变量/参数T尚未解析...但这对于所有通用类型都是适用的,不是吗? - CodyBugstein
@CodyBugstein 是的,这就是为什么语句“我本来期望MyType2可以工作。T扩展了我们上面定义的R应该意味着任何扩展R的类型。”并不太有意义(取决于你对“工作”的理解)。泛型类型别名需要先为T获取一些具体的东西,然后才能进一步解析。 - ford04
1
@stratis 感谢你的提示!- 重命名变量以使其更一致。实际上有错别字的那一行是最后一行,现在是 type T3 = MyType3<{b: string}> (MyType2<T> 是在声明时出现编译错误的情况)。 - ford04
显示剩余6条评论

88
考虑以下代码:
interface Example {
    foo: string
}

type GenericExample<T> = T extends Examlep ? 'foo' : 'bar';

这段代码应该会导致编译错误,因为"Examlep"拼写错误;没有名为"Examlep"的类型,显然程序员想在这里写"Example"。
现在想象一下,在条件类型的"extends"子句中不需要使用"infer"关键字。那么上述代码将不会产生编译错误;它会发现没有名为"Examlep"的类型,推断出它的类型,然后(由于"Examlep"没有约束)观察到"T"确实扩展了推断出的类型的"Examlep"。
在这种情况下,无论"T"是什么,"GenericExample"都将始终是"foo",并且不会有编译错误来通知程序员有关错误。这对于编译器来说几乎总是错误的做法。

4
基本上,infer 的存在是为了替代一些全局的 tsconfig 设置,告诉编译器不允许 undefined 类型。 - CodyBugstein
31
“infer” 的作用是告诉编译器你正在声明一个新类型(在条件类型的范围内)- 就像你必须写“var”,“let”或“const”来告诉编译器你知道你正在声明一个新变量一样。 - kaya3
3
但是 R 不是一个新类型。它只是一个占位符。 - CodyBugstein
20
它是一种类型变量,属于类型的一种。它只存在于条件类型的范围内,因此是新的。 - kaya3
4
这是一个很棒的答案!真正清楚地解释了为什么需要 infer - MEMark
显示剩余4条评论

34
Great! How can I assist you in translating text?
type UnpackArrayType<T> = T extends (infer R)[] ? R: T;
type t1 = UnpackArrayType<number[]>; // t1 is number

UnpackArrayType是一个条件类型。它的解释是:“如果T是(infer R)[]的子类型,返回R。否则,返回T”。

对于类型别名t1,UnpackArrayType中的条件为true,因为number[]与(infer R)[]匹配。在推断过程的结果中,类型变量R被推断为number类型,并从true分支中返回。在UnpackArrayType的范围内声明了一个新的类型变量R,这就是infer的作用,告诉编译器需要声明一个新的类型变量R。

type t2 = UnpackArrayType<string>; //t2 is string

对于 t2,UnpackArrayType 中的条件为假,因为字符串类型与(推断 R)[]不匹配,因此返回字符串。 要了解更多信息,请查看此文章。 https://javascript.plainenglish.io/typescript-infer-keyword-explained-76f4a7208cb0?sk=082cf733b7fc66228c1373ba63d83187

-8
我是这样考虑的:
  1. infer X 替换了 any
使用上面的例子,
type UnpackArrayType<T> = T extends any[] ? T[number]: T;

->

type UnpackArrayType<T> = T extends (infer R)[] ? R: T;
  • X被声明为新类型并同时被捕获。
  • X现在可以在条件语句的true/false部分中使用。

  • 网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接