模式匹配做提取

数组类型

提取数组第一个元素的类型

1
2
type GetFirst<Arr extends unknown[]> =
Arr extends [infer First, ...unknown[]] ? First : never;

提取数组最后一个元素的类型

1
2
type GetLast<Arr extends unknown[]> =
Arr extends [...unknown[], infer Last] ? Last : never;

取去掉最后一个元素的数组

1
2
3
type PopArr<Arr extends unknown[]> = 
Arr extends [] ? []
: Arr extends [...infer Rest, unknown] ? Rest : never;

字符串类型

字符串是否以某个特定字符开头

1
2
type StartWidth<Str extends string, Prefix extends String> = 
Str extends `${Prefix}${string}` ? true : false;

将字符串中某个特定的部分替换成别的字符串

1
2
3
4
5
6
type ReplaceStr<
Str extends string,
From extends string,
To extends string
> = Str extends `${infer Prefix}${From}${infer Suffix}`
? `${Prefix}${To}${Suffix}` : Str;

字符串去除右空格

1
2
3
type TrimStrRight<Str extends string> =
Str extends `${infer Rest}${' ' | '\n' | '\t'}`
? TrimStrRight<Rest> : Str;

函数类型

提取函数参数的类型

1
2
type GetParameters<Func extends Function> = 
Func extends (...args: infer Args) => unknown ? Args : never;

提取函数返回值的类型

1
2
3
type GetReturnType<Func extends Function> = 
Func extends (...args: any[]) => infer ReturnType
? ReturnType : never;

构造器

提取构造器参数的类型

1
2
3
4
type GetInstanceParameters<
ConstructorType extends new (...args: any) => any
> = ConstructorType extends new (...args: infer ParametersType) => any
? ParametersType : never;

提取构造器返回值类型

1
2
3
4
type GetInstanceReturnType<
ConstructorType extends new (...args: any) => any
> = ConstructorType extends new (...args: any) => infer InstanceType
? InstanceType : never;

索引类型

提取 props 中 ref 值的类型

1
2
3
4
5
type GetRefProps<Props> =
'ref' extends keyof Props
? Props extends { ref?: infer Value | undefined}
? Value : never
: never;

重新构造做变换

TypeScript 的 type、infer、类型参数声明的变量都不能修改,想对类型做各种变换产生新的类型就需要重新构造。

数组类型的构造

给数组/元组添加新类型

1
type Push<Arr extends unknown[], Ele> = [...Arr, Ele];

元组重组

1
2
3
4
5
6
7
8
9
10
11
type tuple1 = [1,2];
type tuple2 = ['guang', 'dong'];
// 重组成如下的元组
type tuple = [[1, 'guang'], [2, 'dong']];

// 代码实现
type Zip<One extends unknown[], Other extends unknown[]> =
One extends [infer OneFirst, ...infer OneRest]
? Other extends [infer OtherFirst, ...infer OtherRest]
? [[OneFirst, OtherFirst], ...Zip<OneRest, OtherRest>] : []
: [];

字符串类型的构造

将字符串首字母大写

1
2
3
4
type CapitalizeStr<Str extends string> = 
Str extends `${infer First}${infer Rest}`
? `${Uppercase<First>}${Rest}`
: Str;

将字符串下划线转驼峰

```tstype CamelCase<Str extends string> = Str extends${infer Left}_${infer Right}${infer Rest} ?${Left}${Uppercase}${CamelCase}`
: Str;

1
2
3
4
5
6
7
8

**删除字符串子串**

```ts
type DropSubStr<Str extends string, SubStr extends string> =
Str extends `${infer Prefix}${SubStr}${infer Suffix}`
? DropSubStr<`${Prefix}${Suffix}`, SubStr>
: Str;

函数类型的构造

给函数添加一个参数

1
2
3
4
type AppendArgument<Func extends Function, Arg> = 
Func extends (...args: infer Args) => infer ReturnType
? (...args: [...Args, Arg]) => ReturnType
: never;

索引类型的构造

索引类型是聚合多个元素的类型。比如 class 和对象都是索引类型。索引类型的元素的类型只能是 string、number 或者 Symbol 等类型。

索引类型的每个元素的类型可以添加修饰符:readonly(只读)、?(可选)。

映射类型语法

1
2
3
type Mapping<Obj extends object> = {
[Key in keyof Obj]: Obj[Key]
}

用 as 做重映射改变索引类型的 Key 转成大写

1
2
3
type UppercaseKey<Obj extends object> = {
[Key in keyof Obj as Uppercase<Key & string>]: Obj[Key]
}

因为这里索引的类型可能是 string、number 或 symbol 类型,但是这里转成大写只能是限定为 string。

TS内置高级类型 Record

1
type Record<K extends string | number | symbol, T> = {[P in K]: T}

UppercaseKey 重写版:用 Record 来约束索引类型而不是 object

1
2
3
type UppercaseKey<Obj extends Record<string, any>> = {
[Key in keyof Obj as Uppercase<Key & string>]: Obj[Key]
}

给索引类型添加只读的高级类型

1
2
3
type ToReadonly<T> = {
readonly [Key in keyof T]: T[Key]
}

给索引类型添加可选的高级类型

1
2
3
type ToPartial<T> = {
[Key in keyof T]?: T[Key]
}

给索引类型去掉只读修饰符

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

给索引类型去掉可选修饰符

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

返回特定值的类型的索引类型

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

递归复用做循环

Promise 的递归复用

提取 Promise 值的类型

1
2
3
4
5
6
type DeepPromiseValueType<P extends Promise<unknown>> = 
P extends Promise<infer ValueType>
? ValueType extends Promise<unknown>
? DeepPromiseValueType<ValueType>
: ValueType
: never;

提取 Promise 值的类型简化版

1
2
3
4
type DeepPromiseValueType<T> = 
T extends Promise<infer ValueType>
? DeepPromiseValueType<ValueType>
: never;

数组类型的递归

反转元组

1
2
3
4
type ReversrArr<Arr extends unknown[]> = 
Arr extends [infer First, ...infer Rest]
? [...ReversrArr<Rest>, First]
: Arr;

查找元素

1
2
3
4
5
6
7
8
type Includes<Arr extends unknown[], FindItem> =
Arr extends [infer First, ...infer Rest]
? IsEqual<First, FindItem> extends true
? true
: Includes<Rest, FindItem>
: false;
type IsEqual<A, B> =
(A extends B ? true : false) & (B extends A ? true : false);

删除元素

1
2
3
4
5
6
7
8
9
10
11
type RemoveItem<
Arr extends unknown[],
Item,
Result extends unknown[] = []
> = Arr extends [infer First, ...infer Rest]
? IsEqual<First, Item> extends true
? RemoveItem<Rest, Item, Result>
: RemoveItem<Rest, Item, [...Result, First]>
: Result;
type IsEqual<A, B> =
(A extends B ? true : false) & (B extends A ? true : false);

构造指定类型的数组

1
2
3
4
5
6
7
type BuildArray<
Length extends number,
Ele = unknown,
Arr extends unknown[] = []
> = Arr['length'] extends Length
? Arr
: BuildArray<Length, Ele, [...Arr, Ele]>;

字符串类型的递归

替换子串

1
2
3
4
5
6
7
type ReplaceAll<
Str extends string,
From extends string,
To extends string,
> = Str extends `${infer Left}${From}${infer Right}`
? `${Left}${To}${ReplaceAll<Right, From, To>}`
: Str;

提取字符做联合类型

1
2
3
4
type StringToUnion<Str extends string> = 
Str extends `{infer First}${infer Rest}`
? First | StringToUnion<Rest>
: never;

反转字符串

1
2
3
4
type ReverseStr<Str extends string> = 
Str extends `${infer First}${infer Rest}`
? `${ReverseStr<Rest>}${First}`
: Str;

对象类型的递归

深度递归

1
2
3
4
5
6
7
8
9
10
11
type DeepToReadonly<T extends Record<string, any>> =
T extends any
? {
readonly [Key in keyof T]:
T[Key] extends Object
? T[Key] extends Function
? T[Key]
: DeepToReadonly<T[Key]>
: T[Key]
}
: never;

数组长度做计算

数组长度实现加减乘除

加法

1
2
3
4
5
6
7
8
9
10
type BuildArray<
Length extends number,
Ele = unknown,
Arr extends unknown[] = [],
> = Arr['length'] extends Length
? Arr
: BuildArray<Length, Ele, [...Arr, Ele]>;

type Add<Num1 extends number, Num2 extends number> =
[...BuildArray<Num1>, ...BuildArray<Num2>]['length'];

减法

1
2
3
4
type Subtract<Num1 extends number, Num2 extends number> =
BuildArray<Num1> extends [...BuildArray<Num2>, ...infer Rest]
? Rest['length']
: never;

乘法

1
2
3
4
5
6
7
type Multiple<
Num1 extends number,
Num2 extends number,
ResultArr extends unknown[] = [],
> = Num2 extends 0
? ResultArr['length']
: Multiple<Num1, Subtract<Num2, 1>, [...ResultArr, ...BuildArray<Num1>]>;

除法

1
2
3
4
5
6
7
type Divide<
Num1 extends number,
Num2 extends number,
ResultArr extends unknown[] = [],
> = Num1 extends 0
? ResultArr['length']
: Divide<Subtract<Num1, Num2>, Num2, [...ResultArr, unknown]>;

数组长度实现计数

计算字符串长度

1
2
3
4
5
6
type StrLen<
Str extends string,
CountArr extends unknown[] = [],
> = Str extends `${string}${infer Rest}`
? StrLen<Rest, [...CountArr, unknown]>
: CountArr['length']

比较2个数值谁更大

1
2
3
4
5
6
7
8
9
10
11
type GreaterThan<
Num1 extends number,
Num2 extends number,
CountArr extends unknown[] = [],
> = Num1 extends Num2
? false
: CountArr['length'] extends Num2
? true
: CountArr['length'] extends Num1
? false
: GreaterThan<Num1, Num2, [...CountArr, unknown]>;

实现斐波那契数列

1
2
3
4
5
6
7
8
9
10
type FibonacciLoop<
PrevArr extends unknown[],
CurrentArr extends unknown[],
IndexArr extends unknown[] = [],
Num extends number = 1
> = IndexArr['length'] extends Num
? CurrentArr['length']
: FibonacciLoop<CurrentArr, [...PrevArr, ...CurrentArr], [...IndexArr, unknown], Num>

type Fibonacci<Num extends number> = FibonacciLoop<[1], [], [], Num>;

聚合分散可简化

分布式条件类型

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

1
2
3
type Union = 'a' | 'b' | 'c';
type UppercaseA<Item extends string> =
Item extends 'a' ? Uppercase<Item> : Item;

这和联合类型遇到字符串时的处理一样:

1
2
3
type Union = 'a' | 'b';
type str = `${Union}~`
// type str = 'a~' | 'b~';

数组转联合类型

1
2
3
type Arr = ['a', 'b', 'c'];
type UnionArr = Arr[number];
// type UnionArr = 'a' | 'b' | 'c';

判断是否是联合类型

1
2
3
4
5
6
type isUnion<A, B = A> =
A extends A
? [B] extends [A]
? false
: true
: never;

当 A 是联合类型时:

  • A extends A 这种写法是为了触发分布式条件类型,让每个类型单独传入处理的,没别的意义。
  • A extends A 和 [A] extends [A] 是不同的处理,前者是单个类型和整个类型做判断,后者两边都是整个联合类型,因为只有 extends 左边直接是类型参数才会触发分布式条件类型。

BEM

1
2
3
4
5
type BEM<
Block extends string,
Element extends string[],
Modifiers extends string[]
> = `${Block}__${Element[number]}--${Modifiers[number]}`

全组合

1
2
3
4
5
6
7
8
9
// A 和 B 的全组合
type Combination<A extends string, B extends string> =
A | B | `${A}${B}` | `${B}${A}`

// 全组合
type AllCombinations<A extends string, B extends string = A> =
A extens A
? Combination<A, AllCombinations<Exclude<B, A>>>
: never;

特殊特性要记清

IsAny

any 类型与任何类型的交叉都是 any,也就是 1 & any 结果是 any。

1
type IsAny<T> = 'a' extends (1 & T) ? true : false;

IsEqual

1
2
3
4
5
6
// 以下这种写法不能判断 isAny,isEqual<'a', any> 会返回 true
type IsEqual<A, B> = (A extends B ? true : false) & (B extends A ? true : false);

// 以下这个可以判断 IsEqual2<'a', any> 会返回 false
type IsEqual2<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2)
? true : false;

IsUnion

1
2
3
4
5
6
type IsUnion<A, B> = 
A extends A
? [B] extends [A]
? false
: true
: never;

IsNever

never 在条件类型中也比较特殊,如果条件类型左边是类型参数,并且传入的是 never,那么直接返回 never。

1
2
3
4
5
6
type TestNever<T> = T extends number ? 1 : 2;
// 如下会返回 never
type result = TestNever<never>;

// 正确的 IsNever
type IsNever<T> = [T] extends [never] ? true : false;

除此之外,any 在条件类型中也比较特殊,如果类型参数为 any,会直接返回 trueType 和 falseType 的合并。

1
2
3
type TestAny<T> = T extends number ? 1 : 2;
// 如下会返回 1 | 2
type result = TestAny<any>

IsTuple

元组类型的 length 是数字字面量,而数组的 length 是 number。

1
2
3
4
5
6
7
8
type IsTuple<T> =
T extends [...infer Eles]
? NotEqual<Ele['length'], number>
: false;

type NotEqual<A, B> =
(<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2)
? false : true;

UnionToIntersection

联合类型转交叉类型。

1
2
type UnionToIntersecion<U> =
(U extends U ? (x: U) => unknown : never) extends (x: infer R) => unknown ? R : never;

GetOptional

提取索引类型中的可选索引。

1
2
3
4
5
6
7
8
9
type GetOptional<Obj extends Record<string, any>> = {
[
Key in keyof Obj
as {} extends Pick<Obj, Key> ? Key : never
] : Obj[Key];
}

// Pick 是 TS 内置高级类型
type Pick<T, K extends keyof T> = { [P in K]: T[P]; }

GetRequired

提取索引类型中的非可选索引构造成新的索引类型。

1
2
3
4
5
6
type GetRequired<Obj extends Record<string, any>> = {
[
Key in keyof Obj
as {} extends Pick<Obj, Key> ? never : Key
] : Obj[Key];
}

RemoveIndexSignature

过滤掉索引类型中的可索引签名,构造成一个新的索引类型。
索引签名的特性:索引签名不能构造成字符串字面量类型,因为它没有名字,而其他索引可以

1
2
3
4
5
6
type RemoveIndexSignature<Obj extends Record<string, any>> = {
[
Key in keyof Obj
as Key extends `${infer Str}` ? Str : never
]: Obj[Key]
}

ClassPublicProps

过滤 class 的 public 属性。
根据特性:keyof 只能拿到 class 的 public 索引,private 和 protected 的索引会被忽略。

1
2
3
type ClassPublicProps<Obj extends Record<string, any>> = {
[Key in keyof Obj]: Obj[Key]
}

as const

TypeScript 默认推导出来的类型并不是字面量类型。

1
2
3
4
5
6
7
8
9
const obj = {
a: 1,
b: 2,
}
type objType = typeof obj;
// type objType = {
// a: number;
// b: number
// }

如果想要推到出字面量,就需要用 as const:

1
2
3
4
5
6
7
const arr = [1, 2, 3];
type arrType = typeof arr;
// type arrType = number[];

const arr2 = [1, 2, 3] as const;
type arrType2 = typeof arr2;
// type arrType2 = readonly [1, 2, 3];

反转3个元素的元组类型,需要加上 readonly 才能匹配成功。

1
2
3
4
type ReverseArr<Arr> =
Arr extends readonly [infer A, infer B, infer C]
? [C, B, A]
: never;

练一练

实现 ParseQueryString

将 ‘a=1&b=2&c=3’ 转成 {a: 1, b: 2, c: 3}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
type ParseQueryString<Str extends string> =
Str extends `${infer Param}&${infer Rest}`
? MergeParams<ParseParams<Param>, ParseQueryString<Rest>>
: ParseParam<Str>;

type ParseParams<Param extends string> =
Param extends `${infer Key}=${infer Value}`
? {
[K in Key]: Value
} : Record<string, any>;

type MergeParams<
OneParam extends Record<string, any>,
OtherParam extends Record<string, any>,
> = {
[Key in keyof OneParam | keyof OtherParam]:
Key extends keyof OneParam
? Key extends keyof OtherParam
? MergeValue<OneParam[Key], OtherParam[Key]>
: OneParam[Key]
: Key extends keyof OtherParam
? OtherParam[Key]
: never
}

type MergeValue<One, Other> =
One extends Other
? One
: Other extends unknown[]
? [One, ...Other]
: [One, Other];

TS 内置的高级类型

Parameters

提取函数类型的参数类型

1
2
3
4
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any
? P
: never;

ReturnType

提取函数类型的返回值类型

1
2
3
4
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R
? R
: never;

ConstructorParameters

提取构造函数的参数类型

1
2
3
4
5
type ConstructorParameters<
T extends new (...ars: any) => any
> = T extends new (...args: infer P) => any
? P
: never;

InstanceType

提取构造器返回值类型

1
2
3
4
5
type InstanceType<
T extends new (...ars: any) => any
> = T extends new (...ars: any) => infer R
? R
: any;

ThisParameterType

提取函数参数中 this 的类型

1
2
3
4
type ThisParameterType<T> =
T extends (this: infer U, ...args: any[]) => any
? U
: unknown;

OmitThisParameter

去除函数参数中的 this 类型,并且返回一个新的类型

1
2
3
4
5
6
type OmitThisParameter<T> =
unknown extends ThisParameterType<T>
? T
: T extends (...args: infer A) => infer R
? (...args: A) => infer R
: T;

Partial

把索引类型的所有索引变成可选类型

1
2
3
type Partial<T> = {
[P in keyof T]?: T[P];
}

Required

把索引类型里可选索引改成必选索引

1
2
3
type Required<T> = {
[P in keyof T]-?: T[P];
}

Readonly

索引类型的索引添加只读

1
2
3
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}

Pick

过滤出指定的索引类型

1
2
3
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}

Record

创建索引类型

keyof any 会返回 string | number | symbol

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

如果 Record 里的第一个参数是 string | number | symbol,那么创建的就是索引签名索引类型:

1
2
3
4
type RecordRes = Record<string, number>;
// RecordRes = {
// [x: string]: number;
// }

Exclude

去掉联合类型中的某些类型,即取差集

联合类型当作为类型参数出现在条件类型左边时,会被分散成单个类型传入,这叫做分布式条件类型。

1
type Exclude<T, U> = T extends U ? never : T;

Extract

提取联合类型中的某些类型,即取交集

1
type Extract<T, U> = T extends U ?  T : never;

Omit

去掉某部分索引类型的索引构成新索引类型

1
type Omit<T, K in keyof any> = Pick<T, EXclude<keyof T, K>>;

Awaited

提取 Promise 的返回值类型

1
2
3
4
5
6
7
8
type Awaited<T> = 
T extends null | undefined
? T
: T extends object & { then(onfulfilled: infer F): any}
? F extends ((value, infer V, ...ars: any) => any)
? Awaited<V>
: never
: T;

NonNullable

判断是否是空类型,即不是 null 或 undefined

1
type NonNullable<T> = T extends null | unfefined ? never : T;

Uppercase、Lowercase、Capitalize、Uncapitalize

这几个类型分别是实现大写、小写、首字母大写、去掉首字母大写的。
他们的实现是直接用 js 实现的。

综合实战

KebabCaseToCamelCase

‘aa-bb-cc’ 这种是 KebabCase,而 ‘aaBbCc’ 这种是 CamelCase

1
2
3
4
type KebabCaseToCamelCase<Str extends string> =
Str extends `${infer Item}-${infer Rest}`
? `${Item}${KebabCaseToCamelCase<Capitalize<Rest>>}`
: Str;

CamelCaseToKebabCase

1
2
3
4
5
6
type CamelCaseToKebabCase<Str extends string> =
Str extends `${infer First}${infer Rest}`
? First extends Lowercase<First>
? `${First}${CamelCaseToKebabCase<Rest>}`
: `-${Lowercase<First>}${CamelCaseToKebabCase<Rest>}`
: Str;

Chunk

对数组做分组,比如 1、2、3、4、5 的数组,每两个为 1 组,那就可以分为 1、2 和 3、4 以及 5 这三个 Chunk。

1
2
3
4
5
6
7
8
9
10
type Chunk<
Arr extends unknown[],
ItemLen extends number,
CurItem extends unknown[] = [],
Res extends unknown[] = []
> = Arr extends [infer First, ...infer Rest]
? CurItem['length'] extends ItemLen
? Chunk<Rest, ItemLen, [First], [...Res, CurItem]>
: Chunk<Rest, ItemLen, [...CurItem, First], Res>
: [...Res, CurItem]

TupleToNestedObject

根据数组类型,比如 [‘a’, ‘b’, ‘c’] 的元组类型,再加上值的类型 ‘xxx’,构造出这样的索引类型:

1
2
3
4
5
6
7
{
a: {
b: {
c: 'xxx'
}
}
}
1
2
3
4
5
6
7
8
9
10
11
type TupleToNestedObject<
Tuple extends unknown[],
ValueType
> = Tuple extends [infer First, ...infer Rest]
? {
[Key in First as Key extends keyof any ? Key : never]:
Rest extends unknown[]
? TupleToNestedObject<Rest, ValueType>
: ValueType
}
: ValueType;

PartialObjectPropByKeys

把一个索引类型的某些 Key 转为 可选的,其余的 Key 不变。

1
2
3
4
5
6
7
8
9
10
11
type PartialObjectPropByKeys<
Obj extends Record<string, any>,
Key extends keyof any
> = Partial<Pick<Obj, Extract<keyof Obj, Key>>> & Omit<Obj, Key>

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

函数重载的三种写法

第一种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any) {
return a + b;
}
```

**第二种**

```ts
interface Func {
(a: number, b: number): number;
(a: string, b: string): string;
}
const add: Func = (a: any, b: any) => a + b;

第三种

1
2
type Func = ((a: number, b: number) => number) & ((a: string, b: string): string)
const add: Func = (a: any, b: any) => a + b;

UnionToTuple

将联合类型转成元组。

1
2
3
4
5
6
7
8
9
10
type UnionToTuple<T> = 
UnionToIntersection<
T extends any ? () => T : never
> extends () => infer ReturnType
? [...UnionToTuple<Exclude<T, ReturnType>>, ReturnType]
: [];

// 联合转交叉
type UnionToIntersection<U> =
(U extends U ? (x: U) => unknown : never) extends (x: infer R) => unknown ? R : never;

join

实现一个类似的效果,将:

1
2
const res = join('-')('guang', 'and', 'dong');
// 转成 res = 'guang-and-dong'

join 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
declare function join<
Delimiter extends string
>(delimiter: Delimiter):
<Items extends string[]>
(...parts: Items) => JoinType<Items, Delimiter>;

type JoinType<
Items extends any[],
Delimiter extends string,
Result extends string = ''
> = Items extends [infer First, ...infer Rest]
? JoinType<Rest, Delimiter, `${Result}${First & string}-`>
: RemoveLastDelimiter<Result>;

type RemoveLastDelimiter<Str extends string> =
Str extends `${infer Rest}-`
? Rest
: Str;

AllKeyPath

拿到一个索引类型的所有 key 的路径。

1
2
3
4
5
6
7
8
type AllKeyPath<Obj extends Record<string, any>> = {
[Key in keyof Obj]:
Key extends string
? Obj[Key] extends Record<string, any>
? Key | `${Key}.${AllKeyPath<Obj[Key]>}`
: Key
: never;
}[keyof Obj]

Defaultize

实现这样一个高级类型,对 A、B 两个索引类型做合并,如果是只有 A 中有的不变,如果是 A、B 都有的就变为可选,只有 B 中有的也变为可选。

1
2
3
4
type Defaultize<A, B> = 
& Pick<A, Exclude<keyof A, keyof B>>
& Partial<Pick<A, Extract<keyof A, keyof B>>>
& Partial<Pick<B, Exclude<keyof B, keyof A>>>

infer extends

枚举值转联合类型

以下会把枚举的数值类型转成字符串类型。

1
2
3
4
5
6
7
enum Code {
a = 111,
b = 222,
c = 'abc'
}
type res = `${Code}`
// res = '111' | '222' | 'abc'

StrToNum

使用 infer extends 后就就可以正常使用了。

1
2
3
4
5
6
7
8
9
10
enum Code {
a = 111,
b = 222,
c = 'abc'
}
type StrToNum<Str> = Str extends `${infer Num extends number}`
? Num
: Str;
type res = StrToNum<`${Code}`>
// res = 'abc' | 111 | 222