Skip to content

TypeScript Utility Types


基于TypeScript 5.0.4, 梳理一下通用的Utility Types, 点评实现原理, 帮助大家加深对TypeScript类型运算的理解

常用类型

Awaited<Type>

拆解Promise类型, 获得Promise的返回值类型, 并且可以递归拆解, 直至返回类型中不再出现Promise类型

使用

typescript
type A = Awaited<Promise<string>>;            // type A = string
type B = Awaited<Promise<Promise<number>>>;   // type B = number
type C = Awaited<boolean | Promise<number>>;  // type C = number | boolean

源代码

typescript
/**
 * Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to `never`. This emulates the behavior of `await`.
 */
type Awaited<T> =
    T extends null | undefined ? T : // special case for `null | undefined` when not in `--strictNullChecks` mode
        T extends object & { then(onfulfilled: infer F, ...args: infer _): any } ? // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
            F extends ((value: infer V, ...args: infer _) => any) ? // if the argument to `then` is callable, extracts the first argument
                Awaited<V> : // recursively unwrap the value
                never : // the argument to `then` was not callable
        T; // non-object or non-thenable

实现原理

TIP

T是Union类型时, 条件类型推导的结果也会union成一个Union类型

typescript
type Once<T> = T extends Array<infer I> ? I : T;
type T = string | Array<number>;
type Item = Once<T>; // string | number

TIP

利用泛型(generics)的递归, 来递归拆解类型

typescript
type GetItemValue<T> = T extends Array<infer I> ?  GetItemValue<I> : T;

type A1 = string[];
type A2 = string[][];
type A3 = string[][][];

type I1 = GetItemValue<A1>; // string
type I2 = GetItemValue<A2>; // string
type I3 = GetItemValue<A3>; // string

Partial<Type>

将类型的所有属性变为可选

使用

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

type PartialTodo = Partial<Todo>;
/*
type PartialTodo = {
  title?: string | undefined;
  description?: string | undefined;
}
*/

源代码

typescript
/**
 * Make all properties in T optional
 */
type Partial<T> = {
    [P in keyof T]?: T[P];
};

实现原理

TIP

利用类型映射(type mapping)可以基于已有类型生成新的类型

typeScript
type OptionsFlags<Type> = {
  [Property in keyof Type]: boolean;
};

type FeatureFlags = {
  darkMode: () => void;
  newUserProfile: () => void;
};
 
type FeatureOptions = OptionsFlags<FeatureFlags>;
/*
type FeatureOptions = {
    darkMode: boolean;
    newUserProfile: boolean;
}
*/

TIP

keyof操作符, 可以获取类型的所有属性名, 且属性名的类型为string | number | symbol

typescript
type T = keyof { a: string, b: number, c: boolean }; // type T = "a" | "b" | "c"

TIP

keyof操作符还可以和as子句(clause)结合, 重命名属性名

typescript
type Getters<Type> = {
    [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
 
interface Person {
    name: string;
    age: number;
    location: string;
}
 
type LazyPerson = Getters<Person>;
/*
type LazyPerson = {
    getName: () => string;
    getAge: () => number;
    getLocation: () => string;
}
*/

Required<Type>

将类型的所有属性变为必需

使用

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

type RequiredProps = Required<Props>;
/*
type RequiredProps = {
  a: number;
  b: string;
}
*/

源代码

typescript
/**
 * Make all properties in T required
 */
type Required<T> = {
    [P in keyof T]-?: T[P];
};

实现原理

TIP

readonly and ?是两种类型属性修饰符(modifier), 在类型映射(mapping)时用+-前缀可以增删修饰符, 如果前缀不存在, 默认为+

typescript
interface ReadOnlyType {
  readonly a: number;
  readonly b: string;
}

type WriteableType = {
  -readonly [P in keyof ReadOnlyType]: ReadOnlyType[P];
};
/*
type WriteableType = {
  a: number;
  b: string;
}
*/

Readonly<Type>

将类型的所有属性变为只读

使用

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

type ReadonlyTodo = Readonly<Todo>;
/*
type ReadonlyTodo = {
  readonly title: string;
  readonly description: string;
}
*/

源代码

typescript
/**
 * Make all properties in T readonly
 */
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

实现原理

TIP

参考 Required

Record<Keys, Type>

构造一个对象类型, 其属性名为Keys的类型, 属性值为Type的类型

使用

typescript
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" },
};

源代码

typescript
/**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

实现原理

TIP

keyof anynumber | string | symbol, 因为只有这三种类型可以作为对象的key

typescript
type T = keyof any; // number | string | symbol

Pick<Type, Keys>

构造一个对象类型, 其属性名来自Keys, 其属性值的类型和Type中的同名属性相同

使用

typescript
interface Todo {
  readonly title: string;
  description?: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, 'title' | 'description'>;
/*
type TodoPreview = {
    readonly title: string;
    description?: string | undefined;
}
*/

源代码

typescript
/**
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

实现原理

TIP

K extends keyof T意味着只能使用T中已经存在的属性名

Omit<Type, Keys>

构造一个新类型, 除了Keys中的属性名外, 把Type中的所有属性及类型复制过来

使用

typescript
interface Todo {
  readonly title: string;
  description?: string;
  completed: boolean;
}

type TodoTitle = Omit<Todo, 'title' | 'completed'>;
/*
type TodoTitle = {
    readonly title: string;
}
*/

源代码

typescript
/**
 * Construct a type with the properties of T except for those in type K.
 */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

实现原理

TIP

OmitPickExclude的组合, 先取出所有属性名, 再排除Keys中的属性名, 最后再从Type中取出剩余的属性名

Exclude<UnionType, ExcludedMembers>

UnionType中排除ExcludedMembers的类型

使用

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

源代码

typescript
/**
 * Exclude from T those types that are assignable to U
 */
type Exclude<T, U> = T extends U ? never : T;

实现原理

TIP

T extends U ? never : T意味着如果TU的子类型, 则返回never, 否则返回T

TIP

利用T | never = T 的特性, 可以排除U类型

Extract<Type, Union>

Type中提取Union的类型, 即TypeUnion的交集

使用

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

源代码

typescript
/**
 * Extract from T those types that are assignable to U
 */
type Extract<T, U> = T extends U ? T : never;

实现原理

TIP

参考 Exclude

NonNullable<Type>

Type中排除nullundefined

使用

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

源代码

typescript
type NonNullable<T> = T & {};

实现原理

TIP

这里着重解释一下&类型操作符, 也就是所谓的交集类型, 很多人最初不理解为啥A和B的交集类型是合并属性, 不是做交集么?

实际上, 从集合论的观点, A & B生成的新类型是A和B的子集, 也就是A和B子类型, 或者说A包含AB且B包含AB;

所以AB extends A AB extends B都是true, 那么当然要把AB的属性都合并起来, 否则无法满足即是A的子类型, 又是B的子类型;

typescript
interface A {
  a: string;
}
interface B {
  b: string;
}
type AB = A & B;
/*
type AB = {
  a: string;
  b: string;
}
*/

TIP

再看NonNullable的具体实现T & {}, 令:

T = string | number | null | undefined

那么:

T & {} = (string & {}) | (number & {}) | (null & {}) | (undefined & {})

上述运算的结果就是 string | number, 成功剔除nullundefined

TIP

这里利用了{}(empty object type)类型的特殊之处, 所有类型都是{}类型的子类型, void null undefined除外, 并且{}和这三个类型是互斥的, 不存在既是null又是{}(空对象)的类型, 可以参考以下代码

typescript
declare const neverValue: never;
declare const voidValue: void;
let v: {} = {};

v = neverValue;   // 记住: 右值类型永远是左值类型的子类型!
v = 2;
v = { a: 'c', b: 1 };
v = true;

v = voidValue;    // ERROR
v = null;         // ERROR
v = undefined;    // ERROR

type Never1 = null & {};      // never
type Never2 = undefined & {}; // never
type Never3 = void & {};      // never, 虽然没有代码提示, 但类型为`void`的值是不存在的

Parameters<Type>

获取函数类型的参数类型, 返回一个元组类型

使用

typescript
type T0 = Parameters<(a: string, b: number) => string>;  // [string, number]

源代码

typescript
/**
 * Obtain the parameters of a function type in a tuple
 */
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

实现原理

TIP

(...args: any) => any表示任意函数类型

typescript
type F = (...args: any) => any;
let f: F = () => {};
f = (a: string) => a;
f = (a: string, b: number) => true;

TIP

T extends (...args: infer P)使用infer子句可以推导T的参数类型

ConstructorParameters<Type>

获取构造函数类型的参数类型, 返回一个元组类型

使用

typescript
type T0 = ConstructorParameters<ErrorConstructor>;     // [message?: string]
type T1 = ConstructorParameters<FunctionConstructor>;  // string[]
type T2 = ConstructorParameters<RegExpConstructor>;    // [pattern: string | RegExp, flags?: string]
type T3 = ConstructorParameters<any>;                  // unknown[]

源代码

typescript
/**
 * Obtain the parameters of a constructor function type in a tuple
 */
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;

实现原理

TIP

new (...args: any) => any表示任意构造函数类型, 类似Java中的Class类, 也就是class类型, 这里加上abstract是为了包含abstract class, 保证ConstructorParameters也能用于获取抽象类的构造器参数

实际上, new (...args: any) => anyabstract new (...args: any) => any的子类型

typescript
type BF = abstract new (...args: any) => any;
type F = new (...args: any) => any;

abstract class A {}
class B {}

let f: F;
f = B;
f = A;               // ERROR
f = function() {};   // ERROR

let bf: BF;
bf = B;
bf = A;
bf = function() {};  // ERROR

ReturnType<Type>

获取函数类型的返回值类型

使用

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

源代码

typescript
/**
 * Obtain the return type of a function type
 */
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

实现原理

TIP

参考 Parameters

InstanceType<Type>

获取构造函数类型的实例类型, 其在类似反射的场景中非常有用

使用

typescript
class A {}
type AConstructor = typeof A;
type T0 = InstanceType<AConstructor>;  // A
const o: T0 = new A();
typescript
function createInstance<T extends new (...args: any) => any>(clazz: T): InstanceType<T> {
  return new clazz();
}
class A {}

const instance = createInstance(A);

源代码

typescript
/**
 * Obtain the return type of a constructor function type
 */
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;

实现原理

TIP

参考 ReturnType

ThisParameterType<Type>

获取函数类型的this类型, 其在一些通过函数共享代码逻辑的场景中非常有用

使用

首先, 我们定义一个函数, 仅能在Number类型上调用, 代码如下:

typescript
function toHex(this: Number) {
  return this.toString(16);
}

然后, 我们利用ThisParameterType可以限制toHex的调用者类型, 代码如下:

typescript
function numberToString(n: ThisParameterType<typeof toHex>) {
  return toHex.apply(n);
}

源代码

typescript
/**
 * Extracts the type of the 'this' parameter of a function type, or 'unknown' if the function type has no 'this' parameter.
 */
type ThisParameterType<T> = T extends (this: infer U, ...args: never) => any ? U : unknown;

实现原理

TIP

参考 Parameters

OmitThisParameter<Type>

this参数类型从函数类型中去除

使用

typescript
function toHex(this: Number) {
  return this.toString(16);
}

const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);

console.log(fiveToHex());

源代码

typescript
/**
 * Removes the 'this' parameter from a function type.
 */
type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T;

实现原理

TIP

注意条件类型的写法unknown extends ThisParameterType<T>, 结合ThisParameterType的实现代码, 可以理解为ThisParameterType返回unknown时, 表示T不是函数类型, 直接返回T, 否则返回函数类型的参数类型和返回值类型

WARNING

以上写法为什么不能写成ThisParameterType<T> extends unknown呢?
因为A extends B表示AB的子类型, 在TypeScript中, unknownanytop type, 即所有其他类型都是这两个类型的子类型, 所以ThisParameterType<T> extends unknown永远为true

TIP

anyunknown都是top type, unknown是更安全版本的any, 而类型声明为top type的变量, 可以把任何类型的值赋给它
但是any类型的值也可以赋给任何其他类型, 除了never

typescript
declare const anyValue: any;
let num: number = anyValue;
let str: string = anyValue;
let unknownValue: unknown = anyValue;
let nullValue: null = anyValue;
let undefinedValue: undefined = anyValue;
let voidValue: void = anyValue;
let neverValue: never = anyValue;     // ERROR

TIP

never则是bottom type, 也就是说never是任何类型的子类型

typescript
declare const neverValue: never;
let num: number = neverValue;
let str: string = neverValue;
let anyValue: any = neverValue;
let unknownValue: unknown = neverValue;
let nullValue: null = neverValue;
let undefinedValue: undefined = neverValue;
let voidValue: void = neverValue;

WARNING

理论上T extends never永远为false, 但是TypeScript中有一些特例:

typescript
type Never<T> = T extends never ? true : false;
type Number1 = Never<boolean>;      // false 
type Number2 = Never<string>;       // false 
type Number3 = Never<void>;         // false 
type Number4 = Never<unknown>;      // false 
type Number5 = Never<{}>;           // false 
type NumberOrString = Never<any>;   // false | true = boolean
type Never1 = Never<never>;         // never

DANGER

这里不再继续展开讨论, 但是要注意, any unknown never 这三个类型出现在条件类型中 extends 的左右两边时, 会有很多奇异的行为!!!!

ThisType<Type>

ThisType实际上是一个interface, 用于标记this类型

使用

WARNING

开启 noImplicitThis 编译选项后, this类型不再会被推断为any类型, 此时需要使用ThisType标记this类型

typescript
type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
};
 
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
  let data: object = desc.data || {};
  let methods: object = desc.methods || {};
  return { ...data, ...methods } as D & M;
}
 
let obj = makeObject({
  data: { x: 0, y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx; // Strongly typed this
      this.y += dy; // Strongly typed this
    },
  },
});

 
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);

源代码

typescript
/**
 * Marker for contextual 'this' type
 */
interface ThisType<T> { }

实现原理

TIP

空白的接口, 用于标记this类型

Intrinsic String Manipulation Types

TypeScript提供了一些内置的字符串操作类型, 用于操作字符串类型, 由tsc编译器内部实现(intrinsic)

Uppercase<StringType>

Lowercase<StringType>

Capitalize<StringType>

Uncapitalize<StringType>

Released under the MIT License.