TypeScript 类型工具

TypeScript 提供了一些内置的类型工具,用来方便地处理各种类型,以及生成新的类型。

TypeScript 内置了17个类型工具,可以直接使用。

Awaited<Type>

Awaited<Type>用来取出 Promise 的返回值类型,适合用在描述then()方法和 await 命令的参数类型。

  1. // string
  2. type A = Awaited<Promise<string>>;

上面示例中,Awaited<Type>会返回 Promise 的返回值类型(string)。

它也可以返回多重 Promise 的返回值类型。

  1. // number
  2. type B = Awaited<Promise<Promise<number>>>;

如果它的类型参数不是 Promise 类型,那么就会原样返回。

  1. // number | boolean
  2. type C = Awaited<boolean | Promise<number>>;

上面示例中,类型参数是一个联合类型,其中的boolean会原样返回,所以最终返回的是number|boolean

Awaited<Type>的实现如下。

  1. type Awaited<T> =
  2. T extends null | undefined ? T :
  3. T extends object & {
  4. then(
  5. onfulfilled: infer F,
  6. ...args: infer _
  7. ): any;
  8. } ? F extends (
  9. value: infer V,
  10. ...args: infer _
  11. ) => any ? Awaited<...> : never:
  12. T;

ConstructorParameters<Type>

ConstructorParameters<Type>提取构造方法Type的参数类型,组成一个元组类型返回。

  1. type T1 = ConstructorParameters<
  2. new (x: string, y: number) => object
  3. >; // [x: string, y: number]
  4. type T2 = ConstructorParameters<
  5. new (x?: string) => object
  6. >; // [x?: string | undefined]

它可以返回一些内置构造方法的参数类型。

  1. type T1 = ConstructorParameters<
  2. ErrorConstructor
  3. >; // [message?: string]
  4. type T2 = ConstructorParameters<
  5. FunctionConstructor
  6. >; // string[]
  7. type T3 = ConstructorParameters<
  8. RegExpConstructor
  9. >; // [pattern:string|RegExp, flags?:string]

如果参数类型不是构造方法,就会报错。

  1. type T1 = ConstructorParameters<string>; // 报错
  2. type T2 = ConstructorParameters<Function>; // 报错

any类型和never类型是两个特殊值,分别返回unknown[]never

  1. type T1 = ConstructorParameters<any>; // unknown[]
  2. type T2 = ConstructorParameters<never>; // never

ConstructorParameters<Type>的实现如下。

  1. type ConstructorParameters<
  2. T extends abstract new (...args: any) => any
  3. > = T extends abstract new (...args: infer P)
  4. => any ? P : never

Exclude<UnionType, ExcludedMembers>

Exclude<UnionType, ExcludedMembers>用来从联合类型UnionType里面,删除某些类型ExcludedMembers,组成一个新的类型返回。

  1. type T1 = Exclude<'a'|'b'|'c', 'a'>; // 'b'|'c'
  2. type T2 = Exclude<'a'|'b'|'c', 'a'|'b'>; // 'c'
  3. type T3 = Exclude<string|(() => void), Function>; // string
  4. type T4 = Exclude<string | string[], any[]>; // string
  5. type T5 = Exclude<(() => void) | null, Function>; // null
  6. type T6 = Exclude<200 | 400, 200 | 201>; // 400
  7. type T7 = Exclude<number, boolean>; // number

Exclude<UnionType, ExcludedMembers>的实现如下。

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

上面代码中,等号右边的部分,表示先判断T是否兼容U,如果是的就返回never类型,否则返回当前类型T。由于never类型是任何其他类型的子类型,它跟其他类型组成联合类型时,可以直接将never类型从联合类型中“消掉”,因此Exclude<T, U>就相当于删除兼容的类型,剩下不兼容的类型。

Extract<Type, Union>

Extract<UnionType, Union>用来从联合类型UnionType之中,提取指定类型Union,组成一个新类型返回。它与Exclude<T, U>正好相反。

  1. type T1 = Extract<'a'|'b'|'c', 'a'>; // 'a'
  2. type T2 = Extract<'a'|'b'|'c', 'a'|'b'>; // 'a'|'b'
  3. type T3 = Extract<'a'|'b'|'c', 'a'|'d'>; // 'a'
  4. type T4 = Extract<string | string[], any[]>; // string[]
  5. type T5 = Extract<(() => void) | null, Function>; // () => void
  6. type T6 = Extract<200 | 400, 200 | 201>; // 200

如果参数类型Union不包含在联合类型UnionType之中,则返回never类型。

  1. type T = Extract<string|number, boolean>; // never

Extract<UnionType, Union>的实现如下。

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

InstanceType<Type>

InstanceType<Type>提取构造函数的返回值的类型(即实例类型),参数Type是一个构造函数,等同于构造函数的ReturnType<Type>

  1. type T = InstanceType<
  2. new () => object
  3. >; // object

上面示例中,类型参数是一个构造函数new () => object,返回值是该构造函数的实例类型(object)。

下面是一些例子。

  1. type A = InstanceType<ErrorConstructor>; // Error
  2. type B = InstanceType<FunctionConstructor>; // Function
  3. type C = InstanceType<RegExpConstructor>; // RegExp

上面示例中,InstanceType<T>的参数都是 TypeScript 内置的原生对象的构造函数类型,InstanceType<T>的返回值就是这些构造函数的实例类型。

由于 Class 作为类型,代表实例类型。要获取它的构造方法,必须把它当成值,然后用typeof运算符获取它的构造方法类型。

  1. class C {
  2. x = 0;
  3. y = 0;
  4. }
  5. type T = InstanceType<typeof C>; // C

上面示例中,typeof CC的构造方法类型,然后 InstanceType 就能获得实例类型,即C本身。

如果类型参数不是构造方法,就会报错。

  1. type T1 = InstanceType<string>; // 报错
  2. type T2 = InstanceType<Function>; // 报错

如果类型参数是anynever两个特殊值,分别返回anynever

  1. type T1 = InstanceType<any>; // any
  2. type T2 = InstanceType<never>; // never

InstanceType<Type>的实现如下。

  1. type InstanceType<
  2. T extends abstract new (...args:any) => any
  3. > = T extends abstract new (...args: any) => infer R ? R :
  4. any;

NonNullable<Type>

NonNullable<Type>用来从联合类型Type删除null类型和undefined类型,组成一个新类型返回,也就是返回Type的非空类型版本。

  1. // string|number
  2. type T1 = NonNullable<string|number|undefined>;
  3. // string[]
  4. type T2 = NonNullable<string[]|null|undefined>;
  5. type T3 = NonNullable<boolean>; // boolean
  6. type T4 = NonNullable<number|null>; // number
  7. type T5 = NonNullable<string|undefined>; // string
  8. type T6 = NonNullable<null|undefined>; // never

NonNullable<Type>的实现如下。

  1. type NonNullable<T> = T & {}

上面代码中,T & {}等同于求T & Object的交叉类型。由于 TypeScript 的非空值都属于Object的子类型,所以会返回自身;而nullundefined不属于Object,会返回never类型。

Omit<Type, Keys>

Omit<Type, Keys>用来从对象类型Type中,删除指定的属性Keys,组成一个新的对象类型返回。

  1. interface A {
  2. x: number;
  3. y: number;
  4. }
  5. type T1 = Omit<A, 'x'>; // { y: number }
  6. type T2 = Omit<A, 'y'>; // { x: number }
  7. type T3 = Omit<A, 'x' | 'y'>; // { }

上面示例中,Omit<Type, Keys>从对象类型A里面删除指定属性,返回剩下的属性。

指定删除的键名Keys可以是对象类型Type中不存在的属性,但必须兼容string|number|symbol

  1. interface A {
  2. x: number;
  3. y: number;
  4. }
  5. type T = Omit<A, 'z'>; // { x: number; y: number }

上面示例中,对象类型A中不存在属性z,所以就原样返回了。

Omit<Type, Keys>的实现如下。

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

OmitThisParameter<Type>

OmitThisParameter<Type>从函数类型中移除 this 参数。

  1. function toHex(this: Number) {
  2. return this.toString(16);
  3. }
  4. type T = OmitThisParameter<typeof toHex>; // () => string

上面示例中,OmitThisParameter<T>给出了函数toHex()的类型,并将其中的this参数删除。

如果函数没有 this 参数,则返回原始函数类型。

OmitThisParameter<Type>的实现如下。

  1. type OmitThisParameter<T> =
  2. unknown extends ThisParameterType<T> ? T :
  3. T extends (...args: infer A) => infer R ?
  4. (...args: A) => R : T;

Parameters<Type>

Parameters<Type>从函数类型Type里面提取参数类型,组成一个元组返回。

  1. type T1 = Parameters<() => string>; // []
  2. type T2 = Parameters<(s:string) => void>; // [s:string]
  3. type T3 = Parameters<<T>(arg: T) => T>; // [arg: unknown]
  4. type T4 = Parameters<
  5. (x:{ a: number; b: string }) => void
  6. >; // [x: { a: number, b: string }]
  7. type T5 = Parameters<
  8. (a:number, b:number) => number
  9. >; // [a:number, b:number]

上面示例中,Parameters<Type>的返回值会包括函数的参数名,这一点需要注意。

如果参数类型Type不是带有参数的函数形式,会报错。

  1. // 报错
  2. type T1 = Parameters<string>;
  3. // 报错
  4. type T2 = Parameters<Function>;

由于anynever是两个特殊值,会返回unknown[]never

  1. type T1 = Parameters<any>; // unknown[]
  2. type T2 = Parameters<never>; // never

Parameters<Type>主要用于从外部模块提供的函数类型中,获取参数类型。

  1. interface SecretName {
  2. first: string;
  3. last: string;
  4. }
  5. interface SecretSanta {
  6. name: SecretName;
  7. gift: string;
  8. }
  9. export function getGift(
  10. name: SecretName,
  11. gift: string
  12. ): SecretSanta {
  13. // ...
  14. }

上面示例中,模块只输出了函数getGift(),没有输出参数SecretName和返回值SecretSanta。这时就可以通过Parameters<T>ReturnType<T>拿到这两个接口类型。

  1. type ParaT = Parameters<typeof getGift>[0]; // SecretName
  2. type ReturnT = ReturnType<typeof getGift>; // SecretSanta

Parameters<Type>的实现如下。

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

Partial<Type>

Partial<Type>返回一个新类型,将参数类型Type的所有属性变为可选属性。

  1. interface A {
  2. x: number;
  3. y: number;
  4. }
  5. type T = Partial<A>; // { x?: number; y?: number; }

Partial<Type>的实现如下。

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

Pick<Type, Keys>

Pick<Type, Keys>返回一个新的对象类型,第一个参数Type是一个对象类型,第二个参数KeysType里面被选定的键名。

  1. interface A {
  2. x: number;
  3. y: number;
  4. }
  5. type T1 = Pick<A, 'x'>; // { x: number }
  6. type T2 = Pick<A, 'y'>; // { y: number }
  7. type T3 = Pick<A, 'x'|'y'>; // { x: number; y: number }

上面示例中,Pick<Type, Keys>会从对象类型A里面挑出指定的键名,组成一个新的对象类型。

指定的键名Keys必须是对象键名Type里面已经存在的键名,否则会报错。

  1. interface A {
  2. x: number;
  3. y: number;
  4. }
  5. type T = Pick<A, 'z'>; // 报错

上面示例中,对象类型A不存在键名z,所以报错了。

Pick<Type, Keys>的实现如下。

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

Readonly<Type>

Readonly<Type>返回一个新类型,将参数类型Type的所有属性变为只读属性。

  1. interface A {
  2. x: number;
  3. y?: number;
  4. }
  5. // { readonly x: number; readonly y?: number; }
  6. type T = Readonly<A>;

上面示例中,y是可选属性,Readonly<Type>不会改变这一点,只会让y变成只读。

Readonly<Type>的实现如下。

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

我们可以自定义类型工具Mutable<Type>,将参数类型的所有属性变成可变属性。

  1. type Mutable<T> = {
  2. -readonly [P in keyof T]: T[P];
  3. };

上面代码中,-readonly表示去除属性的只读标志。

相应地,+readonly就表示增加只读标志,等同于readonly。因此,ReadOnly<Type>的实现也可以写成下面这样。

  1. type Readonly<T> = {
  2. +readonly [P in keyof T]: T[P];
  3. };

Readonly<Type>可以与Partial<Type>结合使用,将所有属性变成只读的可选属性。

  1. interface Person {
  2. name: string;
  3. age: number;
  4. }
  5. const worker: Readonly<Partial<Person>>
  6. = { name: '张三' };
  7. worker.name = '李四'; // 报错

Record<Keys, Type>

Record<Keys, Type>返回一个对象类型,参数Keys用作键名,参数Type用作键值类型。

  1. // { a: number }
  2. type T = Record<'a', number>;

上面示例中,Record<Keys, Type>的第一个参数a,用作对象的键名,第二个参数numbera的键值类型。

参数Keys可以是联合类型,这时会依次展开为多个键。

  1. // { a: number, b: number }
  2. type T = Record<'a'|'b', number>;

上面示例中,第一个参数是联合类型'a'|'b',展开成两个键名ab

如果参数Type是联合类型,就表明键值是联合类型。

  1. // { a: number|string }
  2. type T = Record<'a', number|string>;

参数Keys的类型必须兼容string|number|symbol,否则不能用作键名,会报错。

Record<Keys, Type>的实现如下。

  1. type Record<K extends string|number|symbol, T>
  2. = { [P in K]: T; }

Required<Type>

Required<Type>返回一个新类型,将参数类型Type的所有属性变为必选属性。它与Partial<Type>的作用正好相反。

  1. interface A {
  2. x?: number;
  3. y: number;
  4. }
  5. type T = Required<A>; // { x: number; y: number; }

Required<Type>的实现如下。

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

上面代码中,符号-?表示去除可选属性的“问号”,使其变成必选属性。

相对应地,符号+?表示增加可选属性的“问号”,等同于?。因此,前面的Partial<Type>的定义也可以写成下面这样。

  1. type Partial<T> = {
  2. [P in keyof T]+?: T[P];
  3. };

ReadonlyArray<Type>

ReadonlyArray<Type>用来生成一个只读数组类型,类型参数Type表示数组成员的类型。

  1. const values: ReadonlyArray<string>
  2. = ['a', 'b', 'c'];
  3. values[0] = 'x'; // 报错
  4. values.push('x'); // 报错
  5. values.pop(); // 报错
  6. values.splice(1, 1); // 报错

上面示例中,变量values的类型是一个只读数组,所以修改成员会报错,并且那些会修改源数组的方法push()pop()splice()等都不存在。

ReadonlyArray<Type>的实现如下。

  1. interface ReadonlyArray<T> {
  2. readonly length: number;
  3. readonly [n: number]: T;
  4. // ...
  5. }

ReturnType<Type>

ReturnType<Type>提取函数类型Type的返回值类型,作为一个新类型返回。

  1. type T1 = ReturnType<() => string>; // string
  2. type T2 = ReturnType<() => {
  3. a: string; b: number
  4. }>; // { a: string; b: number }
  5. type T3 = ReturnType<(s:string) => void>; // void
  6. type T4 = ReturnType<() => () => any[]>; // () => any[]
  7. type T5 = ReturnType<typeof Math.random>; // number
  8. type T6 = ReturnType<typeof Array.isArray>; // boolean

如果参数类型是泛型函数,返回值取决于泛型类型。如果泛型不带有限制条件,就会返回unknown

  1. type T1 = ReturnType<<T>() => T>; // unknown
  2. type T2 = ReturnType<
  3. <T extends U, U extends number[]>() => T
  4. >; // number[]

如果类型不是函数,会报错。

  1. type T1 = ReturnType<boolean>; // 报错
  2. type T2 = ReturnType<Function>; // 报错

anynever是两个特殊值,分别返回anynever

  1. type T1 = ReturnType<any>; // any
  2. type T2 = ReturnType<never>; // never

ReturnType<Type>的实现如下。

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

ThisParameterType<Type>

ThisParameterType<Type>提取函数类型中this参数的类型。

  1. function toHex(this: Number) {
  2. return this.toString(16);
  3. }
  4. type T = ThisParameterType<typeof toHex>; // number

如果函数没有this参数,则返回unknown

ThisParameterType<Type>的实现如下。

  1. type ThisParameterType<T> =
  2. T extends (
  3. this: infer U,
  4. ...args: never
  5. ) => any ? U : unknown

ThisType<Type>

ThisType<Type>不返回类型,只用来跟其他类型组成交叉类型,用来提示 TypeScript 其他类型里面的this的类型。

  1. interface HelperThisValue {
  2. logError: (error:string) => void;
  3. }
  4. let helperFunctions:
  5. { [name: string]: Function } &
  6. ThisType<HelperThisValue>
  7. = {
  8. hello: function() {
  9. this.logError("Error: Something wrong!"); // 正确
  10. this.update(); // 报错
  11. }
  12. }

上面示例中,变量helperFunctions的类型是一个正常的对象类型与ThisType<HelperThisValue>组成的交叉类型。

这里的ThisType的作用是提示 TypeScript,变量helperFunctionsthis应该满足HelperThisValue的条件。所以,this.logError()可以正确调用,而this.update()会报错,因为HelperThisValue里面没有这个方法。

注意,使用这个类型工具时,必须打开noImplicitThis设置。

下面是另一个例子。

  1. let obj: ThisType<{ x: number }> &
  2. { getX: () => number };
  3. obj = {
  4. getX() {
  5. return this.x + this.y; // 报错
  6. },
  7. };

上面示例中,getX()里面的this.y会报错,因为根据ThisType<{ x: number }>,这个对象的this不包含属性y

ThisType<Type>的实现就是一个空接口。

  1. interface ThisType<T> { }

字符串类型工具

TypeScript 内置了四个字符串类型工具,专门用来操作字符串类型。这四个工具类型都定义在 TypeScript 自带的.d.ts文件里面。

它们的实现都是在底层调用 JavaScript 引擎提供 JavaScript 字符操作方法。

Uppercase<StringType>

Uppercase<StringType>将字符串类型的每个字符转为大写。

  1. type A = 'hello';
  2. // "HELLO"
  3. type B = Uppercase<A>;

上面示例中,Uppercase<T>将 hello 转为 HELLO。

Lowercase<StringType>

Lowercase<StringType>将字符串的每个字符转为小写。

  1. type A = 'HELLO';
  2. // "hello"
  3. type B = Lowercase<A>;

上面示例中,Lowercase<T>将 HELLO 转为 hello。

Capitalize<StringType>

Capitalize<StringType>将字符串的第一个字符转为大写。

  1. type A = 'hello';
  2. // "Hello"
  3. type B = Capitalize<A>;

上面示例中,Capitalize<T>将 hello 转为 Hello。

Uncapitalize<StringType>

Uncapitalize<StringType> 将字符串的第一个字符转为小写。

  1. type A = 'HELLO';
  2. // "hELLO"
  3. type B = Uncapitalize<A>;

上面示例中,Uncapitalize<T>将 HELLO 转为 hELLO。

参考链接