常用技巧

typeof - 获取变量的类型

typeof 关键词除了做 类型保护,还可以从 实现 推断出 类型

注意:此时的 typeof 是一个 类型关键词,只可以用在类型语法中

  1. const a: number = 3;
  2. const b: typeof a = 4;
  3. // 相当于 const b: number = 4
  4. const person = {
  5. name: 'Tom',
  6. age: 20
  7. }
  8. type Person = typeof person
  9. // { name: string; age: number }
  10. function fn(x: string) {
  11. return x.length
  12. }
  13. type Fn = typeof fn
  14. // (x: string) => number

在一个典型的服务端项目中,我们经常需要把一些工具塞到 context 中,如config,logger,db models, utils 等,此时就使用到 typeof

  1. import logger from './logger'
  2. import utils from './utils'
  3. interface Context extends KoaContect {
  4. logger: typeof logger,
  5. utils: typeof utils
  6. }
  7. app.use((ctx: Context) => {
  8. ctx.logger.info('hello, world')
  9. // 会报错,因为 logger.ts 中没有暴露此方法,可以最大限度的避免拼写错误
  10. ctx.loger.info('hello, world')
  11. })

keyof - 获取类型的 键

interface Person {
    name: string;
  age: number;
}

type Keys = keyof Person
// 相当于 type Keys = "name" | "age"

组合 typeof 与 keyof - 捕获键的名称

const colors = {
  red: 'red',
  blue: 'blue'
}

type Colors = keyof typeof colors

let color: Colors // 'red' | 'blue'
color = 'red' // ok
color = 'blue' // ok
color = 'anythingElse' // Error

in - 遍历类型的 键

in 关键词只能用在 type 关键词下面

type Person = {
    [key in 'name' | 'age']: number
}
// Person 为:{ name: number; age: number }


interface Square {
    kind: 'square';
  size: number
}
type res = (radius: number) => { [T in keyof Square]: Square[T] } // T 为 键,Square[T] 为 键的值
// 相当于: type res = (radius) => { kind: 'square'; size: number }


interface Person {
    name: string;
  age: number;
}
type Partial<T> = { [P in keyof T]?: T[P] } // P 为 键,T[P] 为 键的值
type PartialPerson = Parital(Person)
// 相当于:
// type PartialPerson = {
//     name?: string | undefined;
//     age?: number | undefined;
// }

[] - 索引访问

interface Person {
  name: string
  age: number
}

type x = Person['name'] // x is string

T[K] - 类型映射,获取类型的值

配合 keyof 一起使用获取一组 key 对应的 value

interface Person {
    name: string
    age: number
}

type V = Person[keyof Person]
// type V = "string" | "number"

T extends U ? X : Y - 条件类型

extends 除了在 继承类 时会使用,还可以用于判断一个类型是否是另一个类型的父类型,并根据判断结果执行不同的类型分支,其使得 TS 类型具备了一定的编程能力。

extends 条件判断规则如下:如果 T 可以赋值给 U,则返回 X,否则返回 Y,如果 TS 无法确定 T 是否可以赋值给 U,则返回 X | Y

type isNumber<T> = T extends number ? true : false

type T1 = isNumber<number> // true
type T2 = isNumber<number> // false

对于 T extends U ? X : Y 来说,还存在一个特性,当 T 是一个联合类型时,会进行条件分发。

type Union = string | number
type isNumber<T> = T extends number ? 'isNUmber' : 'notNumber'

type UnionType = isNumber<Union> // 'notNumber' | 'isNumber'

实际上,extends 运算会变成如下形式:

(string extends number ? 'isNumber' : 'notNumber') | (number extends number ? 'isNumber' : 'notNumber')

Exclude 就是基于此特性,再配合 nerver 关键字的特性实现的:

// 类似于集合操作中的求取差集
type Exclude<T, K> = T extends K ? never : T

type T1 = Exclude<string | number | boolean, string | boolean>  // number

TypeScript 内置的工具类型 Exclude**** 从类型T中剔除所有可以赋值给U的属性,然后构造一个新的类型。新类型的值存在于 T 中且不存在与 U 中,相当于是求两个集合的差集

infer - 延迟类型推断

extends条件类型的子句中,可以使用infer T来捕获指定位置的类型

  • 提取函数类型的参数类型
    // infer P 表示待推断的函数参数类型
    type ParamType<T> = T extends (param: infer P) => any ? P : T;
    
    在这个条件语句 T extends (param: infer P) => any ? P : T 中,infer P 表示待推断的函数参数。
    整句表示为:如果 T 能赋值给 (param: infer P) => any,则结果是 (param: infer P) => any 类型中的参数 P,否则返回为 T。 ```typescript interface User { name: string; age: number; }

type Func = (user: User) => void;

type Param = ParamType; // Param = User type AA = ParamType; // string


- **提取函数类型的返回值类型**
```typescript
// infer P 表示待推断的函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any;

相比于「提取函数类型的参数类型」给出的示例,ReturnType<T> 只是将 infer P 从参数位置移动到返回值位置,因此此时 P 即是表示待推断的返回值类型。

ype Func = () => User;
type Test = ReturnType<Func>; // Test = User
  • 提取 构造函数 中参数(实例)的类型

一个构造函数可以使用 new 来实例化,因此她的类型通常表示如下:

type Constructor = new (...args: any[]) => any;

infer 用于构造函数中时,
既可用于参数位置 new (...args: infer P) => any;
也可用于返回值位置:new (...args: any[]) => infer P;

TypeScript 内置了如下两个映射类型:

// 获取参数类型
type ConstructorParameters<T extends new (...args: any[]) => any> = T extends new (...args: infer P) => any
  ? P
  : never;

// 获取实例类型
type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any;

class TestClass {
  constructor(public name: string, public age: number) {}
}

type Params = ConstructorParameters<typeof TestClass>; // [string, number]

type Instance = InstanceType<typeof TestClass>; // TestClass

never

never**类型表示的是那些永不存在的值的类型**。 例如,never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是never类型,当它们被永不为真的类型保护所约束时。

never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使any也不可以赋值给never

never与类型TT是除unknown外的其他任意类型)union 后结果是类型T,利用never的这个特点可以实现类型消除,例如将某个类型先转换成never,然后再与其他类型 union。

type a = string
type b = number
type c = never
type d = a | b |c
// type d = string | number

type Exclude<T, U> = T extends U ? never : T;
type T = Exclude<string | number, string>   // number

TypeScript 内置的工具类型 Exclude**** 从类型T中剔除所有可以赋值给U的属性,然后构造一个新的类型。新类型的值存在于 T 中且不存在与 U 中,相当于是求两个集合的差集

字典类型

type Record<K extends keyof any, T> = {
    [P in K]: T;
};

interface Dictionary<T> {
  [index: string]: T;
};

interface NumericDictionary<T> {
  [index: number]: T;
};

const data:Dictionary<number> = {
  a: 3,
  b: 4
}

使用 const enum 维护常量表

// 使用 object 维护常量
const TODO_STATUS {
  TODO: 'TODO',
  DONE: 'DONE',
  DOING: 'DOING'
}
// 使用 const enum 维护常量
const enum TODO_STATUS {
  TODO = 'TODO',
  DONE = 'DONE',
  DOING = 'DOING'
}

function todos (status: TODO_STATUS): Todo[];

todos(TODO_STATUS.TODO)

类型工具

TS 内置了一些常用的类型转换工具,熟练掌握这些工具类型不仅可以简化类型定义,而且可以基于此构建更复杂的类型转换。
下面是 TS 内置的所有类型工具,我加了下注释和示例方便理解,你可以先只看示例,测试下能否自行写出对应的类型实现(Playground)。

Partial

Readonly

Record

Pick

Exclude

Extract

NonNullable

ReturnType

InstanceType

Required

ThisType

/**
* 使 T 的所有属性变为为可选的
*
* Partial<{name: string}> // {name?: string | undefined}
*/
type Partial<T> = {
    [P in keyof T]?: T[P];
};

/**
* 使类型 T 的所有属性变为必需的
*
* Required<{name?: string}> // {name: string}
*/
type Required<T> = {
    [P in keyof T]-?: T[P];
};

/**
* 使类型 T 的所有属性变为只读的
*
* Readonly<{name: string}>  // {readonly name: string}
*/
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

/**
* 从类型 T 中挑出所有属性名出现在类型 K 中的属性
*
* Pick<{name: string, age: number}, 'age'>  // {age: number}
*/
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

/**
* 构造一个 key-value 类型,其 key 是类型 K, value 是类型 T
*
* const map: Record<string, number> = {a: 1, b: 2}
*/
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

/**
* 从类型 T 中剔除类型 U
*
* Exclude<'a' | 'b', 'a'>   // 'b'
*/
type Exclude<T, U> = T extends U ? never : T;

/**
* 从类型 T 中挑出类型 U
* 
* Extract<string | number, number> // number
*/
type Extract<T, U> = T extends U ? T : never;

/**
* 从类型 T 中剔除所有属性名出现在类型 K 中的属性
*
* Omit<{name: string, age: number}, 'age'>  // {name: number}
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

/**
* 剔除类型 T 中的 null 和 undefined 子类型
*
* NonNullable<string | null | undefined> // string
*/
type NonNullable<T> = T extends null | undefined ? never : T;

/**
* 获取函数的参数元组(注意是元组不是数组)
* 
* Parameters<(name: string, age: number) => void> // [string, number]
*/
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

/**
* 获取构造函数的参数元组
*
* class Person { constructor(name: string, age: number) { } }
* ConstructorParameters<typeof Person> // [string, number]
* 
* TS 中类有两个方面:实例面、静态面
* typeof Person 表示类的静态面类型
* Person 表示类的静态面实例,如构造函数、静态方法
* Person 也表示类实例的类型,如成员变量、成员方法
* new Person 表示类的实例
*/
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;

/**
* 获取函数的返回类型
*
* ReturnType<() => string> // string
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

/**
* 获取构造函数的返回类型,即类的实例的类型
*
* class Person { constructor(name: string, age: number) { } }
* InstanceType<typeof Person> // Person
*/
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

参考文档:
深入理解TypeScript
TypeScript 技巧集锦
TypeScript 技巧拾遗
TypeScript 高级技巧
TypeScript Handbook(中文版)
你可能不知道的 TypeScript 高级技巧