常用内置工具类型
为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType 等。不过在具体介绍之前,得先了解一些相关的基础知识。
基础知识
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
为什么 A2 和 A3 的值不一样?
如果用于简单的条件判断,则是直接判断
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
构造一个 type,key 为联合类型中的每个子类型,类型为 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> 排除 null 和 undefined 类后组成的新类型
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[]