TS的工具类型

382 阅读6分钟

在 TypeScript 中提供了许多自带的工具类型,因为这些类型都是全局可用的,所以无须导入即可直接使用。了解了基础的工具类型后,我们不仅知道 TypeScript 如何利用前几讲介绍的基础类型知识实现这些工具类型,还知道如何更好地利用这些基础类型,以免重复造轮子,并能通过这些工具类型实现更复杂的类型。

根据使用范围,我们可以将工具类型划分为操作接口类型、联合类型、函数类型、字符串类型这 4 个方向,下面一一介绍。

操作接口类型

Partial

Partial工具类型可以将一个类型的所有属性变为可选,且该工具类型返回的类型是给定类型的所有子集

type Partial<T> = {
  [P in keyof T]?: T[P];
};
interface Person {
  name: string;
  age?: number;
  weight?: number;
}
type PartialPerson = Partial<Person>;
// 相当于
interface PartialPerson {
  name?: string;
  age?: number;
  weight?: number;
}

Required

与 Partial 工具类型相反,Required 工具类型可以将给定类型的所有属性变为必填的;映射类型在键值的后面使用了一个 - 符号,- 与 ? 组合起来表示去除类型的可选属性,因此给定类型的所有属性都变为了必填.

type Required<T> = {

  [P in keyof T]-?: T[P];

};

type RequiredPerson = Required<Person>;
// 相当于
interface RequiredPerson {
  name: string;
  age: number;
  weight: number;
}

Readonly

Readonly 工具类型可以将给定类型的所有属性设为只读,这意味着给定类型的属性不可以被重新赋值

type Readonly<T> = {

  readonly [P in keyof T]: T[P];

};

type ReadonlyPerson = Readonly<Person>;

// 相当于

interface ReadonlyPerson {
  readonly name: string;
  readonly age?: number;
  readonly weight?: number;
}

Pick

工具类型可以从给定的类型中选取出指定的键值,然后组成一个新的类型

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

type NewPerson = Pick<Person, 'name' | 'age'>;

// 相当于

interface NewPerson {
  name: string;
  age?: number;
}

Omit

与 Pick 类型相反,Omit 工具类型的功能是返回去除指定的键值之后返回的新类型

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

type NewPerson = Omit<Person, 'weight'>;

// 相当于

interface NewPerson {
  name: string;
  age?: number;
}

联合类型

Exclude

在介绍 Omit 类型的实现中,我们使用了 Exclude 类型。通过使用 Exclude 类型,我们从接口的所有属性中去除了指定属性,因此,Exclude 的作用就是从联合类型中去除指定的类型。

Exclude 的实现使用了条件类型。如果类型 T 可被分配给类型 U ,则不返回类型 T,否则返回此类型 T ,这样我们就从联合类型中去除了指定的类型。

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

type T = Exclude<'a' | 'b' | 'c', 'a'>; // => 'b' | 'c'

type NewPerson = Omit<Person, 'weight'>;

// 相当于

type NewPerson = Pick<Person, Exclude<keyof Person, 'weight'>>;

// 其中

type ExcludeKeys = Exclude<keyof Person, 'weight'>; // => 'name' | 'age'

Extract

Extract 类型的作用与 Exclude 正好相反,Extract 主要用来从联合类型中提取指定的类型,类似于操作接口类型中的 Pick 类型。

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

type T = Extract<'a' | 'b' | 'c', 'a'>; // => 'a'

intersect

我们发现 Extract 类型相当于取出两个联合类型的交集,我可以基于Extract实现一个获取接口类型交集的工具类型

type Intersect<T, U> = {
  [K in Extract<keyof T, keyof U>]: T[K];
};

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

interface NewPerson {
  name: string;
  age?: number;
}

type T = Intersect<Person, NewPerson>;
// 相当于
type T = {
  name: string;
  age?: number;
};

NonNullable

NonNullable 的作用是从联合类型中去除 null 或者 undefined 的类型

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

// 等同于使用 Exclude

type NonNullable<T> = Exclude<T, null | undefined>;

type T = NonNullable<string | number | undefined | null>; // => string | number

Record

Record 的作用是生成接口类型,然后我们使用传入的泛型参数分别作为接口类型的属性和值。Record 类型接收了两个泛型参数:第一个参数作为接口类型的属性,第二个参数作为接口类型的属性值。

这里的实现限定了第一个泛型参数继承自keyof any,目前,JavaScript 仅支持stringnumbersymbol作为对象的键值

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

type MenuKey = 'home' | 'about' | 'more';

interface Menu {
  label: string;
  hidden?: boolean;
}

const menus: Record<MenuKey, Menu> = {
  about: { label: '关于' },
  home: { label: '主页' },
  more: { label: '更多', hidden: true },
};

函数类型

ConstructorParameters

ConstructorParameters 可以用来获取构造函数的构造参数

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

class Person {
  constructor(name: string, age?: number) {}
}

type T = ConstructorParameters<typeof Person>; // [name: string, age?: number]

Parameters

Parameters 的作用与 ConstructorParameters 类似,Parameters 可以用来获取函数的参数并返回序对

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

type T0 = Parameters<() => void>; // []

type T1 = Parameters<(x: number, y?: string) => void>; // [x: number, y?: string]

ReturnType

ReturnType 的作用是用来获取函数的返回类型

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

type T0 = ReturnType<() => void>; // => void

type T1 = ReturnType<() => string>; // => string

ThisParameterType

ThisParameterType 可以用来获取函数的 this 参数类型。

type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;

type T = ThisParameterType<(this: Number, x: number) => void>; // Number

ThisType

ThisType 的作用是可以在对象字面量中指定 this 的类型。ThisType 不返回转换后的类型,而是通过 ThisType 的泛型参数指定 this 的类型.

如果你想使用这个工具类型,那么需要开启noImplicitThis的 TypeScript 配置

//ThisType 工具类型只是提供了一个空的泛型接口,仅可以在对象字面量上下文中被 TypeScript 识别
interface ThisType<T> {}

// 没有ThisType情况下
const foo = {
    bar() {
         console.log(this.a); // error,在foo中只有bar一个函数,不存在a
    }
}

// 使用ThisType
const foo: { bar: any } & ThisType<{ a: number }> = {
    bar() {
         console.log(this.bar) // error,因为没有在ThisType中定义
         console.log(this.a); // ok
    }
}

foo.bar // ok
foo.a // error,在外面的话,就跟ThisType没有关系了
//ThisType的作用是:提示其下所定义的函数,在函数body中,其调用者的类型是什么。

OmitThisParameter

OmitThisParameter 工具类型主要用来去除函数类型中的 this 类型。如果传入的函数类型没有显式声明 this 类型,那么返回的仍是原来的函数类型。

type OmitThisParameter<T> = unknown extends ThisParameterType<T>

  ? T

  : T extends (...args: infer A) => infer R

  ? (...args: A) => R

  : T;

type T = OmitThisParameter<(this: Number, x: number) => string>; // (x: number) => string

字符串类型

模板字符串

TypeScript 自 4.1版本起开始支持模板字符串字面量类型。为此,TypeScript 也提供了 Uppercase、Lowercase、Capitalize、Uncapitalize这 4 种内置的操作字符串的类型

// 转换字符串字面量到大写字母

type Uppercase<S extends string> = intrinsic;

// 转换字符串字面量到小写字母

type Lowercase<S extends string> = intrinsic;

// 转换字符串字面量的第一个字母为大写字母

type Capitalize<S extends string> = intrinsic;

// 转换字符串字面量的第一个字母为小写字母

type Uncapitalize<S extends string> = intrinsic;

type T0 = Uppercase<'Hello'>; // => 'HELLO'

type T1 = Lowercase<T0>; // => 'hello'

type T2 = Capitalize<T1>; // => 'Hello'

type T3 = Uncapitalize<T2>; // => 'hello'

字符串的转换使用了 JavaScript 中字符串的 toUpperCase 和 toLowerCase 方法,而不是 toLocaleUpperCase 和 toLocaleLowerCase。其中 toUpperCase 和 toLowerCase 采用的是 Unicode 编码默认的大小写转换规则。