在 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 仅支持string、number、symbol作为对象的键值
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 编码默认的大小写转换规则。