基础

type

我们定义一个 Point (二维坐标点)的接口:

  1. interface Point {
  2. x: number;
  3. y: number;
  4. }

当我们想要拓展这个接口,Point3D (三维坐标点):

  1. interface Point3D extends Point {
  2. z: number;
  3. }

我们也可以将接口映射为 type :

  1. type Point3D = Point & { z: number };

使用 type,我们很容易合并两个 interface:

  1. interface A {
  2. a: string;
  3. }
  4. interface B {
  5. b: string;
  6. }
  7. type AB = A & B;

keyof

keyof 可以将 interface/type 的 key 值返回,返回类型为联合类型(Union Types):

  1. interface Book {
  2. author: string;
  3. numPages: number;
  4. price: number;
  5. }
  6. type BookKeys = keyof Book; // 'author' | 'numPages' | 'price';

内置工具类型

假设我们有个 Person 的接口:

  1. interface Person {
  2. name: string;
  3. age: number;
  4. }

我们想要把某个接口定义为全部可选,或者全部只读

// 全部可选
interface PersonPartial {
    name?: string;
    age?: number;
}

// 全部只读
interface PersonReadonly {
    readonly name: string;
    readonly age: number;
}

可以使用内置工具方法:

type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;

Typescript 内置了很多 Utility Types

  • Partial<Type> 构造的类型为设置所有属性可选(Constructs a type with all properties of Type set to optional )
  • Readonly<Type> 构造的类型为设置所有属性只读(Constructs a type with all properties of Type set to readonly)
  • Record<Keys,Type> 构造一个对象类型,它的key是类型Keys,value是类型Type(Constructs an object type whose property keys are Keys and whose property values are Type)
  • Pick<Type, Keys> 构造的类型为从已有类型Type选择属性Keys (Constructs a type by picking the set of properties Keys from Type)
  • Omit<Type, Keys> 构造的类型为从已有类型Type移除属性Keys (Constructs a type by picking all properties from Type and then removing Keys)
  • Exclude<Type, ExcludedUnion> 构造的类型为从已有联合类型Type移除联合类型ExcludedUnion(Constructs a type by excluding from Type all union members that are assignable to ExcludedUnion)
  • Extract<Type, Union> 构造的类型为从已有联合类型Type提取联合类型Union(Constructs a type by extracting from Type all union members that are assignable to Union)
  • NonNullable<Type> 构造排除null和undefined 的类型(Constructs a type by excluding null and undefined from Type)
  • Parameters<Type> 构造的类型为函数参数的元组类型(Constructs a tuple type from the types used in the parameters of a function type Type)
  • ConstructorParameters<Type>
  • ReturnType<Type> 构造的类型为函数的返回类型(Constructs a type consisting of the return type of function Type)
  • InstanceType<Type>
  • Required<Type> 构造的类型设置所有属性必须(Constructs a type consisting of all properties of Type set to required.)
  • ThisParameterType<Type>
  • OmitThisParameter<Type>
  • ThisType<Type>

为了方便记忆,我将以上列表整理为表格对比:

Utility Types 映射类型 映射值
映射对象类型 Partial<Type> Type 为对象类型 映射为可选(optional)
Readonly<Type> Type 为对象类型 映射为只读(readonly)
Required<Type> Type 为对象类型 映射为必须(required)
映射对象类型 Record<Keys,Type> Type 为对象类型
Keys 为对象属性
映射为key是类型Keys,value是类型Type的对象类型
从对象类型中选择/排除 Keys Pick<Type, Keys> Type 为对象类型
Keys 为对象属性
映射从Type选择Keys
Omit<Type, Keys> Type 为对象类型
Keys 为对象属性
映射从Type移除Keys
从联合类型中选择/排除 Union Exclude<Type,ExcludedUnion> Type 为联合类型
ExcludedUnion 为联合类型
映射从Type移除ExcludedUnion
Extract<Type, Union> Type 为联合类型
Union 为联合类型
映射从Type选择Union
映射联合类型 NonNullable<Type> Type 为联合类型 映射为不可为Null 或 undefined
映射函数类型 Parameters<Type> Type 为方法类型 映射函数类型的参数
ReturnType<Type> Type 为方法类型 映射函数类型的返回

除了前面举例的 PartialReadonly, OmitExcludeExtractNonNullableReturnType 感觉也非常有用,所以对这几个在以下举例,其他可以到官方文档中查看例子理解:

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


// ReturnType
type T0 = ReturnType<() => string>;
//    ^ = type T0 = string
type T1 = ReturnType<(s: string) => void>;
//    ^ = type T1 = void
type T2 = ReturnType<<T>() => T>;
//    ^ = type T2 = unknown
declare function f1(): { a: number; b: string };
type T4 = ReturnType<typeof f1>;
//    ^ = type T4 = {
//        a: number;
//        b: string;
//    }

此外,定义一个类型,还可以同时使用多个 Utility Types:

interface Book {
  author: string | null;
  numPages: number;
  price: number;
}
type AuthoredBook = Omit<Book, 'author'> & NonNullable<Pick<Book,'author'>>;
// 返回一个 author 不为 null 的类型
//    type AuthoredBook = {
//              author: string;
//              numPages: number;
//              price: number;
//    }

创建工具类型

TypeScript如何创建工具类型

以 Omit 为例,其实它是两个工具类型结合使用的。

在开始了解之前,我们先来看看如何获取到接口/类型(interface/type)的属性:

interface Demo {
  a: string;
  b: string;
}
type a = Demo['a']; // 我们可以通过 [] 访问到属性 a

现在我们来看看 Omit 是如何做的:

// definition of Omit
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
//definition of Exclude
type Exclude<T, U> = T extends U ? never : T;
//definition of Pick
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };

创建自己的工具类型

现在我们来创建自己的工具类型。我们创建一个工具类,使得对象的所有key和value 都属于某个特定的类型 GenericKeyObject

// GenericString:key是string类型,value是类型
type GenericString = { [key: string]: string }

// 把 string 改为泛型
// GenericObject:key是string类型,value是定义的泛型T
type GenericObject<T> = { [key: string]: T }

// GenericKeyObject:key在,value是定义的泛型T ????
type GenericKeyObject<K extends keyof any, T> = {
  [P in K]?: T;
};

现在来看看如何使用的:

type BooleanList = GenericKeyObject<string, boolean>; // key 为string类型,值为boolean类型
const countriesVisited: BooleanList = {
  France: true,
  Italy: true
};

type VisitableCountries = 'Hungary' | 'France' | 'Germany';
type CountriesChecklist = GenericKeyObject<VisitableCountries, boolean>;  // key 为VisitableCountries枚举类型,值为boolean类型
const countriesVisitedCurrentYear: CountriesChecklist = {
  France: true,
  Hungary: true
};

const countriesVisitedLastYear: CountriesChecklist = {
  France: true,
  Belgium: true
}; // 这个会编译失败,因为Belgium不在VisitableCountries枚举中

工具类型深度使用

用映射类型创建几个微不足道的例子太简单了。现在我们来深度使用工具类型。

Unpacked

我们先来复习几个知识点:

  • never 类型:意味着永远没有返回类型
  • 类型推断:复习一下官方文档 type inference
  • 条件类型:通过表达式判断类型是A还是B,T extends U ? X : Y

然后我们把这几个概念放在一起玩:

interface ResponseData {
  data: string[];
  hasMoreItems: boolean;
}
const getData = (): Promise<ResponseData> => {
  const data = {
    data: ['one', 'two', 'three', 'four'],
    hasMoreItems: false
  }
  return Promise.resolve(data);
}

What if you wanted to unpack the type from the Promise returned by getData? Simple: Create your own utility using infer.

如何创建一个类型 Unpacked

type Unpacked<T> = T extends (infer U)[]
  ? U
  : T extends (...args: any[]) => infer U
  ? U
  : T extends Promise<infer U>
  ? U :
  T;
type PromiseResult = Unpacked<ReturnType<typeof getData>>;
// PromiseResult = ResponseData
type FunctionResult = Unpacked<() => string>;
// FunctionResult = string

What’s happening there? You are just inferring the type if T extends a Promise or a Function; otherwise, you are just returning the given type T.

Empty

创建类型 Empty ,当传入空对象,返回true;当传入有属性的接口,返回false。

type Empty<T extends {}> = {} extends Required<T> ? true : false;
type isEmpty = Empty<{}>; // true
type isNotEmpty = Empty<{ name: string}>; // false

这个类型有什么用呢?可以将Empty类型和任何条件类型/映射类型组合成更多的类型,这样可以创建更多的自定义映射类型。

type HttpDataReponse<T> = Empty<T> extends true
  ? never
  : T;
type ClassReponse = HttpDataReponse<{}>; // result be never
type BookReponse = HttpDataReponse<{ author: string}>; // result be { author: string }

https://www.typescriptlang.org/docs/handbook/utility-types.html#instancetypetype
https://www.typescriptlang.org/docs/handbook/advanced-types.html#inference-from-mapped-types
https://www.typescriptlang.org/docs/handbook/type-inference.html
https://medium.com/better-programming/mastering-typescripts-mapped-types-5fa5700385eb
https://itdashu.com/docs/typescriptlesson/2edda/mappedtype.html
https://zhuanlan.zhihu.com/p/45249773