遍历数组
// 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
// ...