【问题标题】: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"'.
 */
}

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案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,这使得它无效。

    【讨论】:

      猜你喜欢
      • 2021-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多