必知必会TypeScript-自定义常用类型工具

1,359 阅读7分钟

前言

TypeScript 内置了一些常用的工具类型,这些工具类型大大方便了开发人员,在学习工具类型之前,我们需要先了解一下高级类型的用法。

高级类型

不同于基础类型,高级类型方便我们灵活的操作和定义类型。

交叉类型(&)

交叉类型是将多个类型合并为一个类型。

type PersonType = { name: string; id: number } & { age: number }

const person: PersonType = {
    id: 1,
    name: 'lisi',
    age: 18
}

联合类型(|)

联合类型表示取值可以为多种类型中的一种.

interface Button {
  type: 'default' | 'primary' | 'danger'
  text: string
}

const btn: Button = {
  type: 'primary', // type 可以选择 'default'、'primary'、'danger' 任意其中一个
  text: '按钮'
}

类型别名(type)

前面提到的交叉类型与联合类型如果有多个地方需要使用,就需要通过类型别名的方式,给这两种类型声明一个别名。

type ButtonType = type: 'default' | 'primary' | 'danger'

类型索引(keyof)

keyof 类似于 Object.keys ,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。

interface Person { 
    name: string; 
    age: number; 
    id?: number; 
}

type K1 = keyof Person // name | age | id
type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ... 
type K3 = keyof { [x: string]: Person }; // string | number

类型约束(extends)

extends 可以用来继承一个类,也可以用来继承一个 interface,但还可以用来判断有条件类型,类型约束简单点说就是把类型的具体化数据类型范围缩小。

type Words = 'a'|'b'|"c";

type W<T> = T extends Words ? true : false;

type WA = W<'a'>; // -> true
type WD = W<'d'>; // -> false

类型映射(in)

in 用来遍历枚举类型。

type Keys = "a" | "b" | "c"

type Obj =  {
  [p in Keys]: any
} // -> { a: any, b: any, c: any }

条件类型(U ? X : Y)

条件类型的语法规则和三元表达式一致,经常用于一些类型不确定的情况。

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

infer

infer表示在extends条件语句中以占位符出现的用来修饰数据类型的关键字,被修饰的数据类型等到使用时才能被推断出来。

infer 出现的位置:

  1. infer 出现在 extends 条件语句后的函数类型的参数类型位置上
  2. infer 出现在 extends 条件语句后的函数返回值类型上
  3. infer 出现在类型的泛型具体化上
type ParamType<T> = T extends (...args: infer P) => any ? P : T;
interface User {
  name: string;
  age: number;
}

type Func = (user: User) => void;

type Param = ParamType<Func>; // Param = User
type AA = ParamType<string>; // string

内置类型

下面我们看一下 TS 内置了哪些工具范型。

Partial

Partial 的作用就是可以将某个类型里的属性全部变为可选项 ?

源码:

type Partial<T> = { 
    [P in keyof T]?: T[P];
};

示例:

interface IUser {
  name: string;
  age: number;
  sex: number;
}

type UserType = Partial<IUser>

type UserType = {
    name?: string | undefined;
    age?: number | undefined;
    sex?: number | undefined;
}

Required

Required 的作用刚好跟 Partial 相反,Partial 是将所有属性改成可选项,Required 则是将所有类型改成必选项。

源码:

type Required<T> = {
    [P in keyof T]-?: T[P];
};

示例:

type UserType2 = Required<UserType>

type UserType2 = {
  name: string;
  age: number;
  sex: number;
}

其中 -? 是代表移除 ? 这个 modifier 的标识。

与之对应的还有个 +? , 这个含义自然与 -? 之前相反, 它是用来把属性变成可选项的,+ 可省略,见 Partial

Readonly

这个类型的作用是将传入的属性变为只读选项。

源码:

type Readonly<T> = { 
    readonly [P in keyof T]: T[P];
};

示例:

type UserType3 = Readonly<UserType> 

type UserType3 = { 
    readonly name: string; 
    readonly age: number; 
    readonly sex: number;
}

给子属性添加 readonly 的标识,如果将上面的 readonly 改成 -readonly, 就是移除子属性的 readonly 标识。

Pick

这个类型则可以将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。

源码:

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

示例:

interface Todo {
  title: string
  completed: boolean
  description: string
}

type TodoPreview = Pick<Todo, "title" | "completed">

const todo: TodoPreview = 
  title: 'Clean room',
  completed: false
}

Record

该类型可以将 K 中所有的属性的值转化为 T 类型。

源码:

type Record<K extends keyof any, T> = {
    [P in K]: T;
};

示例:

type T = Record<'a' | 'b' | 'c', Person>; // -> { a: Person; b: Person; c: Person; }

Exclude

Exclude 将某个类型中属于另一个的类型移除掉。

源码:

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

示例:

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

Extract

Extract 的作用是提取出 T 包含在 U 中的元素,换种更加贴近语义的说法就是从 T 中提取出 U。

源码:

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

示例:

type T2 = Extract<string | number | symbol, string | number> // string | number

ReturnType

该类型的作用是获取函数的返回类型。

源码:

type ReturnType<T extends (...args: any[]) => any> = 
    T extends (...args: any[]) => infer R ? R : any;

示例:

function bar(x: string): Array<string> {
  return [x];
}

type fn = ReturnType<typeof bar>; // -> string[]

ThisType

这个类型是用于指定上下文对象类型的。

源码:

interface ThisType<T> { }

声明中只有一个接口,没有任何的实现,说明这个类型是在 TS 源码层面支持的,而不是通过类型变换。

示例:

interface Person {
    name: string;
    age: number;
}

const obj: ThisType<Person> = {
  dosth() {
    this.name // string
  }
}

这样的话,就可以指定 obj 里的所有方法里的上下文对象改成 Person 这个类型了。 需要注意的是使用 ThisType<T> 时,必须确保 --noImplicitThis 标志设置为 true。

InstanceType

该类型的作用是获取构造函数类型的实例类型。

源码:

type InstanceType<T extends new (...args: any[]) => any> = 
    T extends new (...args: any[]) => infer R ? R : any;

示例:

class C {
    x = 0;
    y = 0;
}

type T1 = InstanceType<typeof C>;  // C
type T2 = InstanceType<any>;  // any
type T3 = InstanceType<never>;  // any
type T4 = InstanceType<string>;  // Error
type T5 = InstanceType<Function>;  // Error

NonNullable

这个类型可以用来过滤类型中的 null 及 undefined 类型。

源码:

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

示例:

type T1 = string | number | null;
type T2 = NonNullable<T1>; // -> string | number;

Parameters

该类型可以获得函数的参数类型组成的元组类型。

源码:

type Parameters<T extends (...args: any[]) => any> = 
    T extends (...args: infer P) => any ? P : never;

示例:

function bar(x: string): Array<string> {
  return [x];
}

type P = Parameters<typeof bar>; // -> [number]

此时 P 的真实类型就是 foo 的参数组成的元组类型 [number]

ConstructorParameters

该类型的作用是获得类的参数类型组成的元组类型,简单获取类构造函数参数类型。

源码:

type ConstructorParameters<T extends new (...args: any[]) => any> = 
    T extends new (...args: infer P) => any ? P : never;\

示例:

class Person {
  private firstName: string;
  private lastName: string;
  
  constructor(firstName: string, lastName: string) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
}

type P = ConstructorParameters<typeof Person>; // -> [string, string]

自定义常用类型

下面是一些比较实用的非内置范型工具类型。

Omit

Omit<T, K extends keyof any> 的作用是使用 T 类型中除了 K 类型的所有属性,来构造一个新的类型。

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

示例:

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Omit<Todo, "description">;

const todo: TodoPreview = {
  title: "room",
  completed: false,
};

Mutable

用来将所有属性的readonly移除:

type Mutable<T> = {
  -readonly [P in keyof T]: T[P]
}

Deferred

相同的属性名称,但使值是一个 Promise,而不是一个具体的值:

type Deferred<T> = {
    [P in keyof T]: Promise<T[P]>;
};

DeepReadonly

DeepReadonly用来深度遍历 T,并将其所有属性变成只读类型。

源码:

type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }

示例:

type DeepReadonly<T> = {
  readonly [P in keyof T]: DeepReadonly<T[P]>;
};

interface A {
  B: { C: number; };
  D: { E: number; }[];
}

const myDeepReadonlyObject: DeepReadonly<A> = {
  B: { C: 1 },
  D: [ { E: 2 } ],
}

myDeepReadonlyObject.B = { C: 2 }; // error :)
myDeepReadonlyObject.B.C = 2; // error :)

约束变量的深层属性为只读。

ConvertNumberToString

ConvertNumberToString用来将number转换为string类型。

type ConvertNumberToString<T> = {
  [K in keyof T]: T[K] extends string ? string : T[K]
}

ValueOf

ValueOfkeyof相对应。取出指定类型的所有 value

type ValueOf<T> = T[keyof T]

PowerPartial

内置的 Partial 有个局限性,就是只支持处理第一层的属性,如果是嵌套多层的就没有效果了,不过可以如下自定义:

type PowerPartial<T> = {
    // 如果是 object,则递归类型
    [U in keyof T]?: T[U] extends object
      ? PowerPartial<T[U]>
      : T[U]
};

Deferred

相同的属性名称,但使值是一个 Promise,而不是一个具体的值:

type Deferred<T> = {
    [P in keyof T]: Promise<T[P]>;
};

Proxify

为 T 的属性添加代理:

type Proxify<T> = {
    [P in keyof T]: { get(): T[P]; set(v: T[P]): void }
};

PromiseReturnType

获取Promise的返回值类型。

源码:

type PromiseReturnType<T extends Promise<any>> = 
    T extends Promise<infer R>  ? R  : any;

示例:

type MyPromise = Promise<string>

type T = PromiseReturnType<MyPromise> /// string

Overwrite

后一个类型重新覆盖前一个类型已有的参数。

源码:

type Overwrite<T, U> = {
    [K in keyof T]: K extends keyof U ? U[K] : T[K];
};

示例:

type Props = { name: string; age: number; visible: boolean };
type NewProps = { age: string; other: string };

type ReplacedProps = Overwrite<Props, NewProps>; 
// { name: string; age: string; visible: boolean; }

参考文章