logoProsperBao

Typescript 重构

2022-06-06 11 · 8min

重新构造

TypeScript 支持 type 、infer 、类型参数来保存任意类型,但它们是不可变的。想要变化就需要重新构造新的类型,并且可以在构造新类型的过程中对原类型做一些过滤和变换。

对于索引类型,这种操作叫做映射类型,对索引做修改的 as 叫做重映射。

数组类型

Push

type Push<Arr extends unknown[], X> = [...Arr, X]

type Res = Push<[1, 2, 3], 4> // [1, 2, 3, 4]

Unshift

type Unshift<Arr extends unknown[], X> = [X, ...Arr]

type Res = Unshift<[1, 2, 3], 4> // [4, 1, 2, 3]

Zip

type Zip<A extends unknown[], B extends unknown[]> = A extends [
  infer AF,
  ...infer AR,
]
  ? B extends [infer BF, ...infer BR]
    ? [[AF, BF], ...Zip<AR, BR>]
    : []
  : []

// 尾递归
type Zip1<
  A extends unknown[],
  B extends unknown[],
  R extends unknown[][] = [],
> = A extends [infer AF, ...infer AR]
  ? B extends [infer BF, ...infer BR]
    ? Zip1<AR, BR, [...R, [AF, BF]]>
    : R
  : R

type Res = Zip<[1, 2], [3, 4]> // [[1, 3], [2, 4]]
type Res1 = Zip1<[1, 2], [3, 4]> // [[1, 3], [2, 4]]

字符串类型

CapitalizeStr

type CapitalizeStr<S extends string> = S extends `${infer F}${infer R}`
  ? `${Uppercase<F>}${R}`
  : S

type Res = CapitalizeStr<'typeScript'> // TypeScript

TypeScript 内置了 Capitalize 实现上述功能

CamelCase

type CamelCase<S extends string> = S extends `${infer F}-${infer R}`
  ? `${F}${Capitalize<CamelCase<R>>}`
  : S

type CamelCase1<S extends string> =
  S extends `${infer F}-${infer R}${infer Rest}`
    ? `${F}${Uppercase<R>}${CamelCase<Rest>}`
    : S

// 尾递归
type CamelCase2<
  S extends string,
  Res extends string = '',
> = S extends `${infer F}-${infer R}${infer Rest}`
  ? CamelCase2<Rest, `${Res}${F}${Uppercase<R>}`>
  : Res

type Res = CamelCase<'aa-bb-cc'> // aaBbCc
type Res1 = CamelCase1<'aa-bb-cc'> // aaBbCc
type Res2 = CamelCase1<'aa-bb-cc'> // aaBbCc

DropSubStr

type DropSubStr<
  S extends string,
  SubStr extends string,
> = S extends `${infer F}${SubStr}${infer R}`
  ? `${F}${DropSubStr<R, SubStr>}`
  : S

// 尾递归
type DropSubStr1<
  S extends string,
  SubStr extends string,
  Res extends string = '',
> = S extends `${infer F}${SubStr}${infer R}`
  ? DropSubStr1<R, SubStr, `${Res}${F}`>
  : Res

// 尾递归
type DropSubStr2<
  S extends string,
  SubStr extends string,
> = S extends `${infer F}${SubStr}${infer R}`
  ? DropSubStr2<`${F}${R}`, SubStr>
  : S

type Res = DropSubStr<'hello~~~', '~'> // hello
type Res1 = DropSubStr1<'hello~~~', '~'> // hello
type Res2 = DropSubStr2<'hello~~~', '~'> // hello

函数类型

AppendArgument

type AppendArgument<F extends (...args: any) => any, Arg> = F extends (
  ...args: infer Args
) => infer R
  ? (...args: [...Args, Arg]) => R
  : never

type Res = AppendArgument<(a: string) => boolean, number> // (args_0: string, args_1: number) => boolean

索引类型

索引类型是聚合多个元素的类型,class、对象等都是索引类型。

索引类型可以添加修饰符 readonly ( 只读 ) 、? ( 可选 ) 。

例如:

interface Person {
  name: string
  readonly age: number
  gender?: string
}

Mapping

type Mapping<O extends Record<string, unknown>> = {
  [P in keyof O]: [O[P], O[P]]
}

type Res = Mapping<{ a: 1 }> // { a: [1, 1] }

UppercaseKey

使用 as 对对象类型的 key 进行修改,叫做重映射。

type UppercaseKey<O extends Record<string, unknown>> = {
  [P in keyof O as Uppercase<P & string>]: O[P]
}

type Res = UppercaseKey<{ a: 1 }> // { A: 1 }

MyRecord

type MyRecord<K extends string | number | symbol, V> = { [P in K]: V }

TypeScript 内置了 Record 完成上述功能。

ToReadonly

type ToReadonly<T> = {
  readonly [P in keyof T]: T[P]
}

type Res = ToReadonly<{ a: 1 }> // { readonly a: 1 }

TypeScript 内置了 Readonly 完成上述功能。

ToPartial

type ToPartial<T> = {
  [P in keyof T]?: T[P]
}

type Res = ToPartial<{ a: 1 }> // { a?: 1 | undefined }

TypeScript 内置了 Partial 完成上述功能。

ToMutable

type ToMutable<T> = {
  -readonly [Key in keyof T]: T[Key]
}

type Res = ToMutable<{ readonly a: 1 }> // { a: 1 }

ToRequired

type ToRequired<T> = {
  [Key in keyof T]-?: T[Key]
}

type Res = ToRequired<{ a?: 1 }> // { a: 1 }

TypeScript 内置了 Required 完成上述功能。

FilterByValueType

type FilterByValueType<Obj extends Record<string, any>, ValueType> = {
  [Key in keyof Obj as ValueType extends Obj[Key] ? Key : never]: Obj[Key]
}

type Res = FilterByValueType<{ a: string; b: number }, string> // { a: string }