为啥 TS 中的泛型接口不能正确推断类型?

Posted

技术标签:

【中文标题】为啥 TS 中的泛型接口不能正确推断类型?【英文标题】:Why can't the generic interface in TS infer the type correctly?为什么 TS 中的泛型接口不能正确推断类型? 【发布时间】:2021-11-10 04:23:40 【问题描述】:

一旦在泛型接口中使用“extends Explicit_value”,TS 的类型系统将变得“愚蠢”,即使代码“100% 正确”。

function fn<T extends "a" | "b">(param: T): T 
    if (param === "a") return "a"/* <-- error
Type '"a"' is not assignable to type 'T'.
  '"a"' is assignable to the constraint of type 'T',
  but 'T' could be instantiated with a different subtype of constraint '"a" | "b"'.
 */
    else return "b"/* <-- error
Type '"b"' is not assignable to type 'T'.
  '"b"' is assignable to the constraint of type 'T',
  but 'T' could be instantiated with a different subtype of constraint '"a" | "b"'.
 */


//that's ok:
function fn2<T extends string>(param: T): T 
    return param


//even this:
function fn3<T extends "a">(): T 
    return "a"/* <-- error
Type '"a"' is not assignable to type 'T'.
  '"a"' is assignable to the constraint of type 'T',
  but 'T' could be instantiated with a different subtype of constraint '"a"'.
 */

【问题讨论】:

【参考方案1】:

我不会说这很愚蠢。它只是安全的。 看看这个例子:

function fn3<T extends "a">(): T 
  return "a" // error



const result = fn3<'a' &  tag: 2 >().tag // 2

这意味着T 扩展a 但不等于'a'。 在上面的示例中,result2,但在运行时它等于 undefined

这就是 TS 给你一个错误的原因。通用参数应与运行时值绑定。就像您在第二个示例中所做的那样。

让我们看看你的第一个例子:

function fn<T extends "a" | "b">(param: T): T 
  if (param === "a") return "a"
  else return "b"




错误:

'"a"' 可分配给类型'T' 的约束,但可以使用约束'"a" | 的不同子类型来实例化'T'。 "b"'

请记住,这并不意味着T 总是等于abT 可以是此约束/联合的任何子类型。 例如,您可以使用never,它是类型系统的底层类型:

const throwError = () => 
  throw Error('Hello')


fn<'a' | 'b'>(throwError())

fn 是否有可能返回ab?不,它会抛出一个错误。也许这不是最好的例子,只是想向您展示不同子类型的含义。

让我们用不同的子类型来调用fn

declare var a: 'a' &  tag: 'hello' 

const result = fn(a).tag // undefined

你可能会说:嘿,你不按规则玩。 'a' &amp; tag: 'hello' 类型在运行时无法表示。事实上并非如此。 tag 在运行时将始终为 undefined。 但是,我们处于类型范围内。我们可以很容易地创建这种类型。

摘要

请不要将extends 视为相等的运算符。这只是意味着T 可能是已定义约束的任何子类型。

P.S. 类型在 TypeScript 中是不可变的。这意味着一旦您创建了带有某些约束的类型T,您将无法返回具有其他约束的相同泛型参数T。我的意思是,在您的第一个示例中,返回类型 T 不能只有 a 或只有 b。永远是a | b

【讨论】:

谢谢。我从来没有想过像'a' &amp; tag: 'hello' 这样的代码。【参考方案2】:

这是目前 TypeScript 类型检查系统的一个限制。这个例子在true | false的情况下可以更好的理解:

function returnSelf<T extends true | false>(param: T): T 
  if (param === true) 
    type CUR_VAL = T; // original `T`, not `true` if it narrowed
    return true;
   else 
    type CUR_VAL = T; // original `T`, not `false` if it narrowed
    return false;
  

如果您open the code in a playground 并将鼠标悬停在CUR_VAL 类型别名上,您会注意到它仍然等于T,而不是缩小后的值。因此,当你尝试返回值时,它仍然认为Ttrue | false,这使得它无效。

【讨论】:

以上是关于为啥 TS 中的泛型接口不能正确推断类型?的主要内容,如果未能解决你的问题,请参考以下文章

来自接口实现的 Typescript 泛型推断

为啥在接口列表的泛型类型中使用私有嵌套类型不是“不一致的可访问性”?

TS泛型类、泛型接口、泛型函数

java泛型接口是怎么一回事,干啥用的

我在写一个java泛型接口实现时为啥报出double为意外的类型啊?源程序如下!

Typescript 推断的泛型类型在条件类型中是未知的