Skip to main content

常用内置工具类型

为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 PartialRequiredReadonlyRecordReturnType 等。不过在具体介绍之前,得先了解一些相关的基础知识。

基础知识

typeof

typeof 操作符可以用来获取一个变量声明或对象的类型。

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

const wyh: Person = { name: 'wyh', age: 18 };
type Wyh = typeof wyh; // -> Person

function toArray(x: number): number[] {
return [x];
}

type Func = typeof toArray; // -> (x: number) => number[]

keyof

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

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

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

在 TypeScript 中支持两种索引签名,数字索引和字符串索引:

interface StringArray1 {
// 字符串索引 -> keyof StringArray => string | number
[index: string]: string;
}

interface StringArray2 {
// 数字索引 -> keyof StringArray1 => number
[index: number]: string;
}

为了同时支持两种索引类型,就得要求数字索引的返回值必须是字符串索引返回值的子类。其中的原因就是当使用数值索引时,JavaScript 在执行索引操作时,会先把数值索引先转换为字符串索引。所以 keyof { [x: string]: Person } 的结果会返回 string | number

in

in 用来遍历枚举类型:

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

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

extends

  • 用于接口,表示继承
interface T1 {
name: string,
}

interface T2 {
sex: number,
}

interface T3 extends T1, T2 {
age: number,
}
tip

接口支持多重继承,语法为逗号隔开。如果是 type 实现继承,则可以使用交叉类型 type A = B & C & D

  • 表示条件类型,可用于条件判断

表示条件判断,如果前面的条件满足,则返回问号后的第一个参数,否则第二个。

type A1 = 'x' extends 'x' ? 1 : 2;
// type A1 = 1

type A2 = 'x' | 'y' extends 'x' ? 1 : 2;
// type A2 = 2

type P<T> = T extends 'x' ? 1 : 2;
type A3 = P<'x' | 'y'>
// type A3 = 1 | 2

为什么 A2A3 的值不一样?

  • 如果用于简单的条件判断,则是直接判断 extends 前面的类型是否可分配给后面的类型

  • 如果 extends 前面的类型是泛型,且泛型传入的是联合类型时,则会依次判断该联合类型的所有子类型是否可分配给 extends 后面的类型(是一个分发的过程),然后将最终的结果组成新的联合类型。

如何阻止 extends 关键词对于联合类型的分发特性。

可以通过简单的元组类型包裹以下:

type P<T> = [T] extends ['x'] ? 1 : 2;
type A4 = P<'x' | 'y'>
// type A4 = 2;

infer

Infer 关键字用于条件中的类型推导。

ReturnType 举例:

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

在这个条件语句 T extends (...args: any[]) => infer R ? R : any 中,infer R 表示待推断的函数参数。

如果 T 能赋值给 (...args: any[]) => infer R,则结果是 (...args: any[]) => infer R 类型中的参数 R,否则返回为 any

常用工具类型

Parameters

获取函数的参数类型,将每个参数类型放在一个元组中。

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
  • 首先 Parameters 约束了参数 T 必须是个函数类型
  • 然后判断 T 是否是函数类型,如果是则使用 inter P 推导出函数的参数类型,并将推导的结果存到类型 P 上,否则就返回 never

示例:

declare function f1(arg: { a: number; b: string }): void;

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

type T1 = Parameters<(s: string) => void>;
// type T1 = [s: string]

type T2 = Parameters<<T>(arg: T) => T>;
// type T2 = [arg: unknown]

type T3 = Parameters<typeof f1>;
// type T3 = [arg: {
// a: number;
// b: string;
// }]

type T4 = Parameters<any>;
// type T4 = unknown[]

type T5 = Parameters<never>;
// type T5 = never

type T6 = Parameters<string>;
// ERROR 类型“string”不满足约束“(...args: any) => any”。

type T7 = Parameters<Function>;
// ERROR 类型“Function”不满足约束“(...args: any) => any”。
// ERROR 类型“Function”提供的内容与签名“(...args: any): any”不匹配。

ReturnType

获取函数的返回值类型

/**
* @desc ReturnType 的实现其实和 Parameters 的基本一样
* 无非是使用 infer R 的位置不一样。
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

示例:

declare function f1(): { a: number; b: string };

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

type T1 = ReturnType<(s: string) => void>;
// type T1 = void

type T2 = ReturnType<<T>() => T>;
// type T2 = unknown

type T3 = ReturnType<<T extends U, U extends number[]>() => T>;
// type T3 = number[]

type T4 = ReturnType<typeof f1>;
// type T4 = {
// a: number;
// b: string;
// }

type T5 = ReturnType<any>;
// type T5 = any

type T6 = ReturnType<never>;
// type T6 = never

type T7 = ReturnType<string>;
// ERROR 类型“string”不满足约束“(...args: any) => any”。

type T8 = ReturnType<Function>;
// ERROR 类型“Function”不满足约束“(...args: any) => any”。
// ERROR 类型“Function”提供的内容与签名“(...args: any): any”不匹配。

Partial

Partial<T>T 的所有属性变成可选的

type Partial<T> = {
[P in keyof T]?: T[P];
};
  • [P in keyof T] 通过映射类型,遍历 T 上的所有属性
  • ?: 设置为属性为可选的
  • T[P] 设置类型为原来的类型

示例:

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

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}

const todo1 = {
title: "organize desk",
description: "clear clutter",
};

const todo2 = updateTodo(todo1, {
description: "throw out trash",
});

Required

Required<T>T 的所有属性变为必选项

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

示例:

interface Props {
a?: number;
b?: string;
}

const obj: Props = { a: 5 };

const obj2: Required<Props> = { a: 5 };
// 类型 "{ a: number; }" 中缺少属性 "b",但类型 "Required<Props>" 中需要该属性。

Readonly

Readonly<T>T 的所有属性变成只读

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

示例:

interface Todo {
title: string;
}

const todo: Readonly<Todo> = {
title: "Delete inactive users",
};

todo.title = "Hello";
// 无法分配到 "title" ,因为它是只读属性。

Pick

挑选一组属性并组成一个新的类型

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

示例:

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

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

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

Record

构造一个 typekey 为联合类型中的每个子类型,类型为 T

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

keyof any 得到的是 string | number | symbol

示例:

/**
* @desc 遍历第一个参数的每个子类型,然后将值设置为第二参数
*/
interface CatInfo {
age: number;
breed: string;
}

type CatName = "miffy" | "boris" | "mordred";

const cats: Record<CatName, CatInfo> = {
miffy: { age: 10, breed: "Persian" },
boris: { age: 5, breed: "Maine Coon" },
mordred: { age: 16, breed: "British Shorthair" },
};

Exclude

Exclude<T, U> 提取存在于 T,但不存在于 U 的类型组成的联合类型。

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

示例:

type T0 = Exclude<"a" | "b" | "c", "a">;
// type T0 = "b" | "c"

type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
// type T1 = "c"

type T2 = Exclude<string | number | (() => void), Function>;
// type T2 = string | number

Extract

Extract<T, U> 提取联合类型 T 和联合类型 U 的所有交集。

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

示例:

type T0 = Extract<"a" | "b" | "c", "a" | "f">;
// type T0 = "a"

type T1 = Extract<string | number | (() => void), Function>;
// type T1 = () => void

Omit

Omit<T, K> 从类型 T 中剔除 K 中的所有属性。

/**
* 可以利用 Pick 实现 Omit
*/
type Omit = Pick<T, Exclude<keyof T, K>>;
  • 利用 Pick 提取我们需要的 keys 组成的类型
  • 因此也就是 Omit = Pick<T, 需要的属性联合>
  • 需要的属性联合,就是从 T 的属性联合中排出存在于联合类型 K 中的,也就是 Exclude<keyof T, K>

如果不利用Pick实现呢?

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

示例:

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

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

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

type TodoInfo = Omit<Todo, "completed" | "createdAt">;

const todoInfo: TodoInfo = {
title: "Pick up kids",
description: "Kindergarten closes at 5pm",
};

NonNullable

NonNullable<T> 排除 nullundefined 类后组成的新类型

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

示例:

type T0 = NonNullable<string | number | undefined>;
// type T0 = string | number

type T1 = NonNullable<string[] | null | undefined>;
// type T1 = string[]

参考文档

distributive-conditional-types

Ts高手篇:22个示例深入讲解Ts最晦涩难懂的高级类型工具

Utility Types