【问题标题】:Generic type extending union is not narrowed by type guard泛型类型扩展联合不会被类型保护缩小
【发布时间】:2020-03-12 00:52:09
【问题描述】:

我尝试复制 Anders 在 Build 2018 (36:45) 上展示的条件类型和泛型示例。他使用条件类型作为返回类型来替代更传统的函数重载。

幻灯片有以下内容:

type Name = { name: string };
type Id = { id: number };
type Check = { enabled: boolean };

type LabelForType<T> =
  T extends string ? Name :
  T extends number ? Id :
  T extends boolean ? Check :
  never;

declare function createLabel<T extends string | number | boolean>(value: T): LabelForType<T>

我试图简化这一点,并提出了以下示例。条件类型在给定string 时返回number,反之亦然,而函数将此条件类型实现为返回类型。

type Return<T> = T extends string ? number : T extends number ? string : never;

function typeSwitch<T extends string | number>(x: T):  Return<T>{
  if (typeof x == "string") {
    return 42;
  } else if (typeof x == "number") {
    return "Hello World!";
  }
  throw new Error("Invalid input"); // needed because TS return analysis doesn't currently factor in complete control flow analysis
}

const x = typeSwitch("qwerty"); // number

但是两个返回语句都显示相同的错误:

Type '42' is not assignable to type 'Return<T>'.(2322)
Type '"Hello World!"' is not assignable to type 'Return<T>'.(2322)

我在这里错过了什么?

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    这就是它不起作用的原因:Typescript 对常规变量执行 control-flow type narrowing,但不像您的 T 那样对 类型变量 执行。类型保护typeof x === "string"可用于将变量x缩小为string,但不能将T缩小为string,因此不尝试。

    这是有道理的,因为T 可能是联合类型string | number,即使x 是一个字符串,所以缩小T 本身或缩小T 的上限是不合理的.从理论上讲,将T 缩小到类似 “一种扩展 string | number 但与string 的交集不是never 的类型是合理的”,但这会增加很多类型系统的复杂性相对较小。除了使用类型断言之外,没有完全通用的方法。例如,在您的代码中,return 42 as Return&lt;T&gt;;

    也就是说,在您的用例中,您根本不需要通用函数;你可以写两个overload signatures:

    // overload signatures
    function typeSwitch(x: string): number;
    function typeSwitch(x: number): string;
    // implementation
    function typeSwitch(x: string | number): string | number {
      if (typeof x === "string") {
        return 42;
      } else {
        // typeof x === "number" here
        return "Hello World!";
      }
    }
    

    Playground Link

    【讨论】:

    • 问题是,为什么它在视频中有效? OPs 代码几乎直接从演示幻灯片复制而来,Anders 明确指出您不需要重载。事实上,如果我复制幻灯片代码,编译器会接受它,但幻灯片上没有具体的实现。
    • 也许这是我缺乏理解的谈话,但如果我必须向编译器断言事物实际上是我所知道的类型,那么这一切的意义何在?它是否比我仅使用 switch 语句编写一个普通的旧函数并断言返回类型更安全?这一切给我们带来了什么?
    • 至于 “有什么意义”,您的选择是拥有一个类型检查器来证明您的 大部分 代码是安全的(并且大多数时间,正确地告诉你你的代码何时不安全),或者没有类型检查器。在计算上不可能拥有一个类型检查器来证明所有类型安全的代码都是类型安全的,但拥有一个偶尔需要您在其证明中填补几个空白的类型检查器几乎没有用处。 Typescript 并不能证明您的代码的所有可证明属性,并且没有尝试过,也没有宣传为尝试过。
    • 没关系,我现在明白了。我试图断言 specific 返回类型,而不仅仅是Return&lt;T&gt;。说得通。希望 OP 会发现它和我一样有用。
    • 啊;好点,是的,如果你写return 42 as number;(或等效的return 42 as Return&lt;string&gt;;),它就行不通了,当然你不应该(也永远不会)需要断言42是一个数字。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-09
    • 2021-02-26
    • 2021-06-06
    • 2018-05-13
    • 1970-01-01
    相关资源
    最近更新 更多