Typescript 类型的模式匹配是通过 extends 对类型参数做匹配,结果保存到通过 infer 声明的局部类型变量里,如果匹配就能从该局部变量里拿到提取出的类型。

数组类型

First

1
2
3
type First<T extends unknown[]> = T extends [infer R, ...unknown[]] ? R : never;
//1
type res = First<["1", 2, 999]>;

类型参数 T 通过 extends 约束为只能是数组类型,数组元素是 unkown 也就是可以是任何值。

对 T 做模式匹配,把我们要提取的第一个元素的类型放到通过 infer 声明的 R 局部变量里,后面的元素可以是任何类型,用 unknown 接收,然后把局部变量 First 返回。

Last

1
2
3
type Last<T extends unknown[]> = T extends [...unknown[], infer R] ? R : never;
//999
type res1 = Last<["1", 2, 999]>;

PopArr

1
2
3
type PopArr<T extends unknown[]> = T extends [...infer U, infer R] ? U : never;
//['1',2]
type res2 = PopArr<["1", 2, 999]>;

ShiftArr

1
2
3
4
5
type ShiftArr<T extends unknown[]> = T extends [infer U, ...infer R]
? R
: never;
//[2,999]
type res3 = ShiftArr<["1", 2, 999]>;

字符串类型

匹配一个模式字符串,把需要提取的部分放到 infer 声明的局部变量里。

判断字符串是否以某个前缀开头 StartsWith

1
2
3
4
5
6
7
8
9
type StartsWith<
Str extends string,
prefix extends string
> = Str extends `${prefix}${string}` ? true : false;

//true
type res4 = StartsWith<"aba bbb ccc", "aba">;
//false
type res5 = StartsWith<"aba bbb ccc", "abc">;

字符串替换 Replace

1
2
3
4
5
6
7
8
9
10
11
type ReplaceStr<
Str extends string,
From extends string,
To extends string
> = Str extends `${infer prefix}${From}${infer SuffFix}`
? `${prefix}${To}${SuffFix}`
: never;
// "li is a bad person"
type res6 = ReplaceStr<"li is a good person", "good", "bad">;
// "li is a good person !"
type res7 = ReplaceStr<"li is a good person ?", "?", "!">;

Trim

实现去掉空白字符的 Trim:

不过因为我们不知道有多少个空白字符,所以只能一个个匹配和去掉,需要递归。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type TrimRight<Str extends string> = Str extends `${infer Rest}${
| " "
| "\n"
| "\r"}`
? TrimRight<Rest>
: Str;
type TrimLeft<Str extends string> = Str extends `${
| " "
| "\n"
| "\r"}${infer Rest}`
? TrimLeft<Rest>
: Str;
//aaa
type res8 = TrimRight<"aaa ">;
// aaa /
type res9 = TrimLeft<" aaa ">;

type Trim<Str extends string> = TrimRight<TrimLeft<Str>>;
//aaa
type res10 = Trim<" aaa ">;

函数类型

参数类型

1
2
3
4
5
6
7
type GetParameters<Fn extends Function> = Fn extends (
...args: infer Args
) => unknown
? Args
: never;
//[name:number]
type res = GetParameters<(name: number) => void>;

返回值类型

1
2
3
4
5
6
7
8
9
type GetReturnType<Fn extends Function> = Fn extends (
// 不能用unknown 涉及到函数参数逆变
// ...args: unknown[]
...args: any[]
) => infer R
? R
: never;
//string
type res1 = GetReturnType<(name: number) => string>;

this 类型

方法里可以调用 this,比如这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Dong {
name: string;

constructor() {
this.name = "dong";
}

hello() {
return 'hello, I\'m ' + this.name;
}
}

const dong = new Dong();
dong.hello();

对象.方法名的方式调用的时候,this 就指向那个对象。

但是方法也可以用 call 或者 apply 调用:

1
dong.hello().call({xxx:1});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Dong {
name: string;
constructor() {
this.name = "dong";
}
hello(this: Dong) {
return "hello, I'm " + this.name;
}
}

const dong = new Dong();
dong.hello();
// 报错
dong.hello.call({ xxx: 1 });

type GetThisParameterType<T> = T extends (this: infer U) => any ? U : never;
type res7 = GetThisParameterType<typeof dong.hello>;

构造器类型

构造器类型可以用 interface 声明,使用 new(): xx 的语法。

1
2
3
4
5
6
7
interface Person {
name: string;
}

interface PersonConstructor {
new (name: string): Person;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Person {
name: string;
}
interface PersonConstructor {
new (name: string): Person;
}

type GetInstanceType<ConstructorType extends new (...args: any) => any> =
ConstructorType extends new (...args: any) => infer InstanceType
? InstanceType
: any;
// Person
type res8 = GetInstanceType<PersonConstructor>;

type GetConstructorParameters<
ConstructorType extends new (...args: any) => any
> = ConstructorType extends new (...args: infer ParametersType) => any
? ParametersType
: never;
// [name: string]
type res9 = GetConstructorParameters<PersonConstructor>;

索引类型

索引类型也同样可以用模式匹配提取某个索引的值的类型

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

类型参数 Props 为待处理的类型。

通过 keyof Props 取出 Props 的所有索引构成的联合类型,判断下 ref 是否在其中,也就是 ‘ref’ extends keyof Props。

在 ts3.0 里面如果没有对应的索引,Obj[Key] 返回的是 {} 而不是 never,所以这样做下兼容处理。

如果有 ref 这个索引的话,就通过 infer 提取 Value 的类型返回,否则返回 never。