typescript(8)- 深入类型 | 青训营笔记

108 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的第15天

「前言」

在之前的文章中,我们讨论过 ts 基础类型ts 高级类型,似乎类型的玩法就这么多

typescript 的类型远不止于此

「前置知识」

字面量类型

type T = 'a';type T = 'a' | 'b'; 这种类型,有一种特殊的名称叫做 字面量类型,顾名思义,类型具有固定的值,可以为 字符串、布尔值、数字、对象、函数、甚至泛型、接口,只要是一个具有确切的固定值的类型都可以作为 字面量类型

type IBookItem
  = { author: string }
  & ({
    type: 'computer';
    range: string;
  }
    | {
      type: 'history';
      theme: string;
    })

在上一篇的文章中,我们用来他来实现类型保护,其中这是运用了字面量类型可穷尽的特点typescript(7)- 高级类型 | 青训营笔记 - 掘金 (juejin.cn),这里我们不过多的探讨类型保护

这种类型与 枚举类型十分类似,他们都有一个显著地特点:可遍历,可穷尽

type IBookItem
  = { author: string }
  & ({
    type: 'computer';
    range: string;
  }
    | {
      type: 'history';
      theme: string;
    })

function test(book: IBookItem) {
  if (book.type! == 'computer' && book.type !== 'history') { } // error
}

对于可穷尽的类型,在分支判断的类型校验十分严格

错误原因

  • 如果 book.type 不是 computer,满足条件,则不会进入分支内部
  • 如果是 computer,那一定不是可穷尽类型中的其他值,所以后续的判断是多于的

「ts 标准库预定义的类型」

TypeScript 2.8在lib.d.ts里增加了一些预定义的有条件类型:

  • Exclude<T, U> -- 从T中剔除可以赋值给U的类型。
  • Extract<T, U> -- 提取T中可以赋值给U的类型。
  • NonNullable<T> -- 从T中剔除nullundefined
  • ReturnType<T> -- 获取函数返回值类型。
  • InstanceType<T> -- 获取构造函数类型的实例类型。

我们来讨论他们的用法和原理实现

Exclude

type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'd'>; // 'b' | 'c'
type T2 = Exclude<'a' | 'd', 'a' | 'b' | 'c'>; // 'd'

类似于数学中的 差集

原理

type Exclude<T, U> = T extends U ? never : T;

看到这里你是不是也想到了 JavaScript 中的三元表达式ts 对这种类型判断叫做 条件类型

type IsNumber<T> = T extends number ? true : false;

type IsNumber<T> = T extends number ? true : false;

type T1 = IsNumber<number> // true
type T2 = IsNumber<1> // true
type T3 = IsNumber<string> // false
type T4 = IsNumber<any> // boolean
type T5 = IsNumber<unknown> // false
type T6 = IsNumber<never> // never
type T7 = IsNumber<number | string> // boolean
type T8 = IsNumber<number & string> // never

对于联合类型,交叉类型这些复合类型使用类型判断等效于

// 等效 T7
type T9 = IsNumber<number> | IsNumber<string>

在运算过程中会被拆解分支

再看下面的例子

type Naked<T> = T extends boolean ? true : false;

type WrappedTuple<T> = [T] extends [boolean] ? true : false;
type WrappedArray<T> = T[] extends boolean[] ? true : false;
type WrappedPromise<T> = Promise<T> extends Promise<boolean> ? true : false;

type T0 = Naked<number | boolean>; // boolean
type T1 = WrappedTuple<number | boolean>; // false
type T2 = WrappedArray<number | boolean>; // false
type T3 = WrappedPromise<number | boolean>; // false

由以上结果可知,如果条件类型中的类型参数 T 被包装过,所以在运算过程中就不会被分解成多个分支。

了解以上特点,现在我们再来看这段代码

type Exclude<T, U> = T extends U ? never : T;

type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'd'>; // 'b' | 'c'
type T2 = Exclude<'a' | 'd', 'a' | 'b' | 'c'>; // 'd'

extends 右边的泛型不会拆分运算步骤,也就是相当于三次 Exclude 结果的联合类型

Extract

type Extract<T, U> = T extends U ? T : never;

Exclude 十分类似,只是交换了结果

NonNullable

type NonNullable<T> = T extends null | undefined ? never : T;

「参考文章」

juejin.cn/post/709626…