前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TypeScript系列教程九《类型转换》-- 条件类型

TypeScript系列教程九《类型转换》-- 条件类型

作者头像
星宇大前端
发布2021-08-05 16:16:40
6770
发布2021-08-05 16:16:40
举报
文章被收录于专栏:大宇笔记大宇笔记大宇笔记

类型转换是TS最好玩也是语言的灵魂,想玩好需要熟练各种手段和工具,下面一一介绍类型转换的一些常用手段。

条件类型


根据输入来决定输出是大多数有用程序的核心,js也不例外。条件判断类型可以根据输入关系决定输出类型。

interface Animal {
    live(): void;
  }
  interface Dog extends Animal {
    woof(): void;
  }
  
  type Example1 = Dog extends Animal ? number : string;
          
//   type Example1 = number
  
  type Example2 = RegExp extends Animal ? number : string;

条件类型看起来有点像js的三目表达式

  SomeType extends OtherType ? TrueType : FalseType;

当extends左边的类型可分配给右边的类型时,您将在第一个分支中获得该类型(“true”分支);否则,您将在后一个分支(“false”分支)中获得类型。

从上面的例子来看,条件类型可能不会立即变得有用——我们可以告诉自己Dog是否扩展了Animal并选择数字或字符串!但是条件类型的威力来自于将它们与泛型一起使用。

让我们看一下createLabel函数的例子:

interface IdLabel {
  id: number /* some fields */;
}
interface NameLabel {
  name: string /* other fields */;
}

function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
  throw "unimplemented";
}

createLabel的这些重载描述了一个JavaScript函数,该函数根据输入的类型进行选择。请注意以下几点:

如果一个库必须在整个API中反复做出相同的选择,那么这将变得很麻烦。

我们必须创建三个重载:当我们确定类型时,每种情况一个重载(一个用于字符串,一个用于数字),另一个重载用于最一般的情况(使用字符串|数字)。对于createLabel可以处理的每一种新类型,重载的数量都呈指数增长。

我们可以将该逻辑编码为条件类型作为替代:

type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;

使用条件类型,改造重载的createLabel 函数并使用。

interface IdLabel {
    id: number /* some fields */;
  }
  interface NameLabel {
    name: string /* other fields */;
  }

type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;

function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
    throw "unimplemented";
  }
  
  let a = createLabel("typescript");
     
//   let a: NameLabel
  
  let b = createLabel(2.8);
     
//   let b: IdLabel
  
  let c = createLabel(Math.random() ? "hello" : 42);
//   let c: NameLabel | IdLabel

条件类型约束

通常,条件类型中的检查会为我们提供一些新信息。就像使用类型保护缩小范围可以为我们提供更具体的类型一样,条件类型的真正分支将通过我们检查的类型进一步约束泛型。

看下面的例子:

type MessageOf<T> = T["message"];
//Type '"message"' cannot be used to index type 'T'.

上面这个例子ts 会检查错误,因为不知道T有没有message属性。我们应该约束T,TS将不再编译报错:

type MessageOf<T extends { message: unknown }> = T["message"];

interface Email {
  message: string;
}

type EmailMessageContents = MessageOf<Email>;

//type EmailMessageContents = string

如果我们想让MessageOf支持所有类型,如果没有message属性则默认返回never, 我们可以在约束的外面加上条件类型

type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;

interface Email {
  message: string;
}

interface Dog {
  bark(): void;
}

type EmailMessageContents = MessageOf<Email>;
              
// type EmailMessageContents = string

type DogMessageContents = MessageOf<Dog>;
             
// type DogMessageContents = never

在正确的分支里面,TS知道T拥有message属性

作为另一个示例,我们还可以编写一个名为flatte的类型,将数组类型展平为其元素类型,但在其他情况下不使用它们:

type Flatten<T> = T extends any[] ? T[number] : T;

// Extracts out the element type.
type Str = Flatten<string[]>;
     
// type Str = string

// Leaves the type alone.
type Num = Flatten<number>;
     
// type Num = number

Flatten是数组类型的时候,用了索引访问类型去获得string[]里元素的类型,其他类型时返回类型本身。

条件类型使用infer

我们只是发现自己使用条件类型来应用约束,然后提取类型。这是一个非常常见的操作,条件类型使它变得更容易。

条件类型为我们提供了一种使用infer关键字从我们在true分支中比较的类型中进行推断的方法。例如,我们可以在Flatte中推断元素类型,而不是使用索引访问类型“手动”将其取出:

type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

在这里,我们使用infer关键字声明性地引入一个名为Item的新泛型类型变量,而不是指定如何在true分支中检索T的元素类型。这使我们不必考虑如何挖掘和探索我们感兴趣的类型的结构。

我们可以使用推断关键字编写一些有用的助手类型别名。例如,对于简单的情况,我们可以从函数类型中提取返回类型:

type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
  ? Return
  : never;

type Num = GetReturnType<() => number>;
     
//type Num = number

type Str = GetReturnType<(x: string) => string>;
     
//type Str = string

type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
      
//type Bools = boolean[]

当从具有多个调用签名的类型(例如重载函数的类型)进行推断时,将从最后一个签名(这可能是最允许的“一网打尽”情况)进行推断。无法基于参数类型列表执行重载解析。

declare function stringOrNum(x: string): number;
declare function stringOrNum(x: number): string;
declare function stringOrNum(x: string | number): string | number;

type T1 = ReturnType<typeof stringOrNum>;
     
//type T1 = string | number

分布式的条件类型

当条件类型作用于泛型类型时,当给定一个联合类型时,它们将成为分布式的。例如,以以下内容为例:

type ToArray<Type> = Type extends any ? Type[] : never;

如果给ToArray传入一个联合类型,那么条件类型将会应用每一个联合类型的成员,然后联合。

type ToArray<Type> = Type extends any ? Type[] : never;

type StrArrOrNumArr = ToArray<string | number>;
           
//type StrArrOrNumArr = string[] | number[]

1、这里发生的是StrOrNumArray分布在:

  string | number;

2、并将工会的每种成员类型映射到有效的:

  ToArray<string> | ToArray<number>;

3、最后得到

  string[] | number[];

通常,分配性是期望的行为。为了避免这种行为,可以用方括号括住extends关键字的每一侧。

type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;

// 'StrOrNumArr' is no longer a union.
type StrOrNumArr = ToArrayNonDist<string | number>;
         
//type StrOrNumArr = (string | number)[]
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-08-03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 条件类型
    • 条件类型约束
      • 条件类型使用infer
        • 分布式的条件类型
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档