通用类型扩展联合不会被类型保护缩小 [英] Generic type extending union is not narrowed by type guard

查看:28
本文介绍了通用类型扩展联合不会被类型保护缩小的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I tried to replicate Anders' example for conditional types and generics which he showed at Build 2018 (36:45). He uses a conditional type as a return type as replacement for more traditional function overloads.

幻灯片有以下内容:

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,反之亦然,而函数将此条件类型实现为返回类型.

I tried to simplify this a bit and came up with the following example. The conditional type returns number when given a string and vice versa, while the function implements this conditional type as return type.

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

然而,两个 return 语句都显示相同的错误:

However both return statements show the same error:

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

我在这里遗漏了什么?

推荐答案

以下是它不起作用的原因:Typescript 执行 控制流类型缩小 常规变量,但不是像您的 T 这样的 类型变量.类型保护 typeof x === "string" 可用于将变量 x 缩小为类型 string,但不能缩小 Tstring,不尝试.

Here's why it doesn't work: Typescript does control-flow type narrowing on regular variables, but not on type variables like your T. The type guard typeof x === "string" can be used to narrow the variable x to type string, but it cannot narrow T to be string, and does not try.

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

This makes sense, because T could be the union type string | number even when x is a string, so it would be unsound to narrow T itself, or to narrow T's upper bound. In theory it would be sound to narrow T to something like "a type which extends string | number but whose intersection with string is not never", but that would add an awful lot of complexity to the type system for relatively little gain. There is no fully general way around this except to use type assertions; for example, in your code, return 42 as Return<T>;.

也就是说,在您的用例中,您根本不需要通用函数;你可以只写两个重载签名:

That said, in your use-case you don't need a generic function at all; you can just write two 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!";
  }
}

游乐场链接

这篇关于通用类型扩展联合不会被类型保护缩小的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆