TypeScript 工具类型

TS utils

Posted by Eric on March 1, 2019

Return Type

使用 infer 来推断函数的返回值

type MyReturnType<T extends (...args:any) => any> = T extends (...args:any) => (infer R) ?
    R :
    any;
// 或者
type MyReturnType<T extends Function> = T extends (...args:any) => (infer R) ?
    R :
    any;

Omit

封装方式

type MyOmit<T, U extends string | number | symbol> = {
    [K in Exclude<keyof T, U>]: T[K]
}

使用方式

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = MyOmit<Todo, "description">;
// TodoPreview = {
//    title: string;
//    completed: boolean;
//}

MyReadonly2

将输入的参数设置为 readony

type MyReadonly<T, U extends string | number | symbol> = {
    readonly [K in Extract<keyof T, U>]: T[K]
} & {
    [K in keyof T]: T[K]
}

或者

type Diff<A, B> = A extends B ? A : never;

type MyReadonly2<T, U extends string | number | symbol> = {readonly [K in Diff<keyof T, U>]: T[K]} & {[K in keyof T]: T[K]};

使用

interface Todo {
  title: string
  description: string
  completed: boolean
}

const todo: MyReadonly2<Todo, 'title' | 'description'> = {
  title: "Hey",
  description: "foobar",
  completed: false,
}
// title 和 description 将会变为 readonly

Deep Readonly

递归使用

使得一个对象内部的每一个值都变为 readonly

type DeepOnly<T> = keyof T extends never ?
  T :
  {readonly [K in keyof T]: DeepOnly<T[K]>}

使用方式

type X = { 
  x: { 
    a: 1
    b: 'hi'
  }
  y: 'hey'
}

type Expected = { 
  readonly x: { 
    readonly a: 1
    readonly b: 'hi'
  }
  readonly y: 'hey' 
}

const todo: DeepReadonly<X> // should be same as `Expected`

TupleToUnion

输入元组,返回联合类型

type TupleToUnion<T> = T extends Array<infer R> ? R : never;

使用方式

type Arr = ['1', '2', '3']

const a: TupleToUnion<Arr> // expected to be '1' | '2' | '3'

Chainable

封装

type Chainable<R = {}> = {
  option<K extends string | number | symbol, V>(key: K, value: V): Chainable<{
    [k in K | keyof R]: k extends K ? V : k extends keyof R ? R[k] : never
  }>
  get(): R
}

使用方式

declare const config: Chainable

const result = config
  .option('foo', 123)
  .option('name', 'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()

Last of Array

封装

type Last<T extends any[]> = T extends [...infer _, infer L] ? L : never;

使用

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

type tail1 = Last<arr1> // expected to be 'c'
type tail2 = Last<arr2> // expected to be 1

Pop Push Shift Unshift

type Pop<T extends unknown[]> = T extends [...infer U, unknown] ? U : never
type Push<T extends unknown[], U> = [...T, U]
type Shift<T extends unknown[]> = T extends [unknown, ...infer U] ? U : never
type Unshift<T extends unknown[], U> = [U, ...T]

Promise function

封装

declare function PromiseAll<T extends any[]>(values: readonly [...T]):
  Promise<{ [K in keyof T]: T[K] extends Promise<infer R> ? R : T[K] }>;

使用

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

// expected to be `Promise<[number, number, string]>`
const p = Promise.all([promise1, promise2, promise3] as const)

Type Lookup

从联合类型里面找出对应的类型

封装, 先将其封装为对象,然后再使用键值来取得

type Lookup<T, U extends string> = {
    [K in U]: T extends {type: U} ? T : never
}[U]

使用

interface Cat {
  type: 'cat'
  breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}

interface Dog {
  type: 'dog'
  breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
  color: 'brown' | 'white' | 'black'
}

type MyDogType = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`