模式匹配
大约 3 分钟
引入
我们知道,字符串可以和正则做模式匹配,找到匹配的部分,提取子组,之后可以用 $1
, $2
等引用匹配的子组
'abc'.replace(/a(b)c/, '$1,$1,$1'); // b,b,b
Typescript 的类型也同样可以做模式匹配
type p = Promise<'decade'>;
type GetValueType<P> = P extends Promise<infer Value> ? Value : never;
type result = GetValueType<p>;
Typescript 类型的模式匹配是通过 extends 对类型参数做匹配,结果保存到通过 infer 声明的局部类型变量里,如果匹配就能从该局部变量里拿到提取出的类型
数组类型
First
type arr = [1, 2, 3];
type GetArrayFirstType<T extends unknown[]> = T extends [
infer First,
...infer Rest,
]
? First
: never;
type FirstType = GetArrayFirstType<arr>;
提示
any 和 unknown 的区别: any 和 unknown 都代表任意类型,但是 unknown 只能接收任意类型的值
,而 any 除了可以接收任意类型的值,也可以赋值给任意类型(除了 never)。类型体操中经常用 unknown 接受和匹配任何类型,而很少把任何类型赋值给某个类型变量。
Last
type arr = [1, 2, 3];
type GetArrayLastType<T extends unknown[]> = T extends [
...infer Rest,
infer Last,
]
? Last
: never;
type LastType = GetArrayLastType<arr>;
Pop
空数组直接返回, 否则返回最后一项
type arr = [1, 2, 3];
type PopArray<T extends unknown[]> = T extends []
? []
: T extends [...infer Rest, infer Last]
? Rest
: never;
type result = PopArray<arr>;
Shift
type arr = [1, 2, 3];
type ShiftArray<T extends unknown[]> = T extends []
? []
: T extends [infer First, ...infer Rest]
? Rest
: never;
type result = ShiftArray<arr>;
字符串类型
字符串类型也同样可以做模式匹配,匹配一个模式字符串,把需要提取的部分放到 infer 声明的局部变量里
StartWith
判断字符串是否以某个前缀开头
type StartWith<
Str extends string,
Prefix extends string,
> = Str extends `${Prefix}${string}` ? true : false;
type result = StartWith<'test', 't'>;
EndWith
type EndWith<
Str extends string,
Prefix extends string,
> = Str extends `${string}${Prefix}` ? true : false;
type result = EndWith<'test', 't'>;
Replace
字符串可以匹配一个模式类型,提取想要的部分,自然也可以用这些再构成一个新的类型
type ReplaceStr<
Str extends string,
From extends string,
To extends string,
> = Str extends `${infer Start}${From}${infer End}`
? `${Start}${To}${End}`
: Str;
type result = ReplaceStr<'decade 2010', '2010', '2009'>;
Trim
能够匹配和替换字符串,那也就能实现去掉空白字符的 Trim:
不过因为我们不知道有多少个空白字符,所以只能一个个匹配和去掉,需要递归
// 先实现 trimRight / trimLeft
type Empty = ' ' | '\n' | '\t';
type TrimRight<Str extends string> = Str extends `${infer Rest}${Empty}`
? TrimRight<Rest>
: Str;
type rightResult = TrimRight<'decade '>;
type TrimLeft<Str extends string> = Str extends `${Empty}${infer Rest}`
? TrimLeft<Rest>
: Str;
type leftResult = TrimLeft<' decade'>;
type Trim<Str extends string> = TrimLeft<TrimRight<Str>>;
type result = Trim<' decade '>;
函数
GetParameters
type GetParameters<Func extends Function> = Func extends (
...args: infer Args
) => unknown
? Args
: never;
type result = GetParameters<(name: string, age: number) => void>;
GetReturnType
type GetReturnType<Func extends Function> = Func extends (
...args: any[]
) => infer ReturnType
? ReturnType
: never;
type result = GetReturnType<() => number>;
GetThisParameterType
type GetThisParameterType<T> = T extends (
this: infer ThisType,
...args: any[]
) => any
? ThisType
: unknown;
class Dong {
name: string;
constructor() {
this.name = 'dong';
}
hello(this: Dong) {
return "hello, I'm " + this.name;
}
}
const dong = new Dong();
type result = GetThisParameterType<typeof dong.hello>;
构造器
构造器和函数的区别是,构造器是用于创建对象的,所以可以被 new
GetInstanceType
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;
type result = GetInstanceType<PersonConstructor>;
GetConstructorParameters
interface Person {
name: string;
}
interface PersonConstructor {
new (name: string): Person;
}
type GetConstructorParameters<
ConstructorType extends new (...args: any) => any,
> = ConstructorType extends new (...args: infer ParametersType) => any
? ParametersType
: never;
type result = GetConstructorParameters<PersonConstructor>;
索引类型
索引类型也同样可以用模式匹配提取某个索引的值的类型
GetRefProps
type GetRefProps<Props> = 'ref' extends keyof Props
? Props extends { ref?: infer Value | undefined }
? Value
: never
: never;
type result = GetRefProps<{ ref: number; age: number }>;
总结
TypeScript 类型的模式匹配是通过类型 extends
一个模式类型,把需要提取的部分放到通过 infer
声明的局部变量里,后面可以从这个局部变量拿到类型做各种后续处理