Skip to content

Latest commit

 

History

History
328 lines (240 loc) · 9.42 KB

类型编程总结.md

File metadata and controls

328 lines (240 loc) · 9.42 KB

遍历数组

// string[][number] -> string
// [1, 2, 3][number] -> 1 | 2 | 3
type MapArr<T extends unknown> = T[number]

遍历对象

type MapObjKeys<T extends object> = keyof T

type MapObjValues<T extends object> = T[keyof T]

判断空数组

type IsEmptyArr<T extends unknown> = T extends [] ? true : false

type IsEmptyArr<T extends unknown> = T['length'] extends 0 ? true : false

遍历对象的方式构建新的数组

// 当T为数组类型时 { [K in keyof T]: T[K] } 会构建一个新的数组类型
type MapArrayKey<T extends unknown[]> = {
  [K in keyof T]: T[K]
}

自己extends自己触发分布式条件类型

当类型参数为联合类型,并且在条件类型左边直接引用该类型参数的时候,TypeScript会把每一个元素单独传入来做类型运算,最后再合并成联合类型。

// 条件类型左边的泛型参数如果是联合类型,会触发分布式条件类型,
// T<1 | 2> -> T<1> | T<2>

// 可以通过自己extends自己触发
type Distributive<U> = U extends U ? [U] : never

// [1] | [2] | [3]
type test = Distributive<1 | 2 | 3> 

"解构"元组的联合类型时,也有分布式条件类型的效果

type TupleUnion = [2, 3] | [4, 5]

// [1, ...[2, 3]] | [1, ...[4, 5]]
type test = [1, ...TupleUnion]

length属性

type Tuple = [1, 2, 3]

// 元组的length是元组具体的长度(数字字面量类型
type TupleLen = tuple['length'] // 3

type Array = number[]

// 数组的length是number类型
type ArrayLen = Array['length'] // number


// 字符串的length也是number类型
type StringLen = '123'['length'] // number

// 如何读取字符串的length为真实的长度 - 构建数组
type ReadStrLen<S extends string, CountArr extends unknown[] = []> = S extends `${infer F}${infer Rest}`
  ? ReadStrLen<Rest, [...CountArr, F]>
  : CountArr['length']

如何区分类型是数组还是元组

// 通过元组和数组的length属性表现的不同,可以判断
// 数组的length是number类型 -> number extends number -> true
type IsTuple<T extends unknown> = number extends T['length'] ? false : true

获取元组的索引组成的联合类型

如给定一个元组[1, 2, 3],返回0 | 1 | 2

// 数组的最大索引比长度小1,因此可以不断取R的length
type GetTupleIndex<T extends unknown[]> = T extends [infer _, ...infer R]
  ? R['length'] | GetTupleIndex<R>
  : 0

// 另一种思路
type GetTupleIndex<T extends unknown[]> = keyof {
  // keyof T取出来的不止有索引,还有一些特殊的属性,比如length
  // 通过infer I extends number约束为索引
  [K in keyof T as K extends `${infer I extends number}` ? I : never]: T[K]
}

元组的长度做计数

// 已知,元组的长度是具体的数字字面量类型,因此可以用元组的长度做计数
type NewArray<N extends number, T extends unknown[] = []> = T['length'] extends N ? T : NewArray<N, [...T, unknown]>

// 如果想求两个数字相加,也就是构造这两个数字长度的元组合并后取length属性
type Add<A extends number, B extends number> = [...NewArray<A>, ...NewArray<B>]['length']

// 如果想求两个数组相减,也可以通过这种方式比较A和B的大小
type Sub<A extends number, B extends number> = NewArray<A> extends [...NewArray<B>, ...infer R] ? R['length'] : never

// 如果想求两个数字A, B相乘,也就是A + ... + A,加B次A
type Mul<A extends number, B extends number, Count extends unknown[] = []> = B extends 0
  ? Count['length']
  : Mul<A, Sub<B, 1>, [...Count, ...NewArray<A>]>

// 如果想求两个数字A, B相除,也就是A - ... -B,减到A为0
type Div<A extends number, B extends number, Count extends unknown[] = []> = A extends 0
  ? Count['length']
  : Div<Sub<A, B>, B, [...Count, unknown]>

取数组最后一个元素

// 数组类型可以通过索引取指定值的类型
type Arr = [1, 2, 3]

type test = Arr[0] // 1 

// 在JS中取数组最后一个元素可以通过length - 1,但是TS无法直接进行算数运算,可以通过构造一个长度+1的数组
type GetLast<T extends unknown> = [any, ...T][T['length]']

获取一个类型的原始类型

// 可以通过valueOf匹配出来
type GetPrimitiveType<T extends unknown> = T extends { valueOf: () => infer R } ? R : T

数组和字符串的模式匹配差异

// 这个匹配的是第一个成员F和最后一个成员L以及中间的所有成员R
type MatchArr<T extends number[]> = T extends [infer F, ...infer R, infer L] ? R : T

// 这个匹配的是第一个子字符F和第二个子字符R以及后面的所有子字符L
type MatchStr<S extends string> = S extends `${infer F}${infer R}${infer L}` ? R : S

"..."可以用于数组的任意位置

// 放在前面
type DelLast<T extends number[]> = T extends [...infer R, infer L] ? R : T

// 放在后面
type DelFirst<T extends number[]> = T extends [infer F, ...infer R] ? R : T

// 放在中间
type DelFirstAndLast<T extends number[]> = T extends [infer F, ...infer R, ...infer L] ? R: T

在字符串中判断是否存在某个子字符

// 通过${string}能匹配任意字符(包括空字符)的规律,这样就可以匹配任意位置的字符
// `${string}${Sub}${string}`换成`${any}${Sub}${any}`也可以
type MatchStr<S extends string, Sub extends string> = S extends `${string}${Sub}${string}`
  ? true
  : false

type test1 = MatchStr<'abc', 'a'> // true
type test2 = MatchStr<'abc', 'b'> // true
type test3 = MatchStr<'abc', 'c'> // true

在字符串中判断是否存在某个重复的子字符

// 通过${string}能匹配任意字符(包括空字符)的规律
type IsRepeatedSub<S extends string, Sub extends string> = S extends `${string}${Sub}${string}${Sub}${string}`
  ? true
  : false

判断对象属性是可选?还是必需-?

// 获取对象所有可选属性的键
type OptionalKeys<T extends object> = keyof {
  // 这里的as是重映射
  [K in keyof T as T[K] extends Required<T>[K] ? never : K ]: T[K]
}

// 不用内置泛型实现
type OptionalKeys<T extends object> = keyof {
  [K in keyof T as { [P in K]: T[P] } extends { [P in K]-?: T[P] } ? never: K ]: T[K]
}


// 获取对象所有必需的键(和上面反过来即可
type NeedKeys<T extends object> = keyof {
  [K in keyof T as T[K] extends Required<T>[K] ? K : never ]: T[K]
}

判断类型相等

// 两个条件类型判断相关性的时候会判断右边部分是否相等

// 如果是两个条件类型 T1 extends U1 ? X1 : Y1 和 T2 extends U2 ? X2 : Y2 相关的话,那 T1 和 T2 相关、X1 和 X2 相关、Y1 和 Y2 相关,而 U1 和 U2 相等。
type IsEqual<T, U> = (<X>() => X extends T ? 1 : 2) extends (<X>() => X extends U ? 1 : 2)
  ? true
  : false

判断字符是否是字母

// 如果一个字符的的大写和小写相关,则为非字母
// Uppercase<1> -> 1  Lowercase<1> -> 1
type IsLetter<S extends string> = Uppercase<S> extends Lowercase<S> ? false : true

把字符串字面量类型转成数字字面量类型

// infer 的时候加上 extends 来约束推导的类型为number类型
type StringToNumber<S extends string> = S extends `${infer N extends number}` ? N : S

type test = StringToNumber<'123'> // 123

keyof作用于两个对象&、|

type A = {
  name: string;
  age: number
}

type B = {
  name: string;
  nickname: string
}

// 取A和B键的并集
// 相当于keyof A | keyof B
type test1 = keyof (A & b)

// 取A和B键的交集
// 相当于keyof A & keyof B
type test2 = keyof (A | b)

去除联合类型中的某个指定类型

// 假设T为1 | 2 | 3,U为1,这里触发分布式条件类型
// 当执行到1 extends 1时,满足条件得到never,2和3不满足条件得到原值
// 最后就是2 | 3 | never,never会被联合类型过滤掉,所以结果就是2 | 3
type RemoveTypeFromUnion<T, U> = T extends U ? never : T

// 如果需要排除的是null或者undefined
// T extends null | undefined ? never : T;
type RemoveNullOrUndefined<T> = T & {}

联合类型转交叉类型

// 协变位置中同意类型变量的多个候选项会推断出联合类型
type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;
type T10 = Foo<{ a: string; b: string }>; // string
type T11 = Foo<{ a: string; b: number }>; // string | number

// 逆变位置中同一类型变量的多个候选项会推断出交集类型
type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
  ? U
  : never;
type T20 = Bar<{ a: (x: string) => void; b: (x: string) => void }>; // string
type T21 = Bar<{ a: (x: string) => void; b: (x: number) => void }>; // string & number

// 利用函数参数的逆变特征
type UnionToIntersection<T> = (
  T extends unknown ? (x: T) => any : never
) extends (x: infer R) => any
  ? R
  : never;

函数重载的类型推断

// 从多个调用签名的类型(例如函数重载)进行推断时,将从最后一个签名进行推断
declare function foo(x: string): number;
declare function foo(x: number): string;
type test = ReturnType<typeof foo>; // string


declare function bar(x: number): string;
declare function bar(x: string): number;
type test2 = ReturnType<typeof bar>; // number

函数类型的交集

// 函数参数处于逆变位置,因此当函数相交时,参数不会相交,而是合并
// 这种函数的交集就形成了一个重载
type h = ((x: 1) => 0) & ((x: 2) => 0)


// 相当于
function h(x: 1): 0
function h(x: 2): 0
// ...