TS类型技巧(一):提取
Extends + Infer
type GetValueType<P> = P extends Promise<infer Value>
? Value
: never
// '123'
type a = GetValueType<Promise<'123'>>
extends 对 P 做类型匹配
顺便 infer 声明并储存变量
如果extends匹配成立,就返回infer 储存的变量,否则就返回 never。
1. 数组 提取类型
type arr = [1, 2, 3]
// 其中的<Arr extends unknown[]>约束了GetFirst接收的必须是一个数组
type GetFirst<Arr extends unknown[]> = Arr extends [
infer First,
...unknown[]
]
? First
: never
type a = GetFirst<arr> // 1
2. 字符串 提取类型
type GetStartWord<Str extends string> =
Str extends `${infer Prefix} ${string}` ? Prefix : never
type GetStartChar<Str extends string> =
Str extends `${infer Prefix}${string}` ? Prefix : never
type StartsWith<
Str extends string,
Prefix extends string
> = Str extends `${Prefix}${string}` ? true : false
type a = GetStartWord<'hello world'> // 'hello'
type b = GetStartChar<'hello world'> // 'h'
type c = StartsWith<'hello world', 'h'> // true
借此可以对字符串的改动进行约束
Replace
// 替换一段内容,仅限一段
type ReplaceStr<
Str extends string,
From extends string,
To extends string
> = Str extends `${infer Prefix}${From}${infer Suffix}`
? `${Prefix}${To}${Suffix}`
: Str
// 'hello typescript'
type e = ReplaceStr<'hello world', 'world', 'typescript'>
ReplaceAll
// 替换所有内容(递归)
type ReplaceStrAll<
Str extends string,
From extends string,
To extends string
> = Str extends `${infer Prefix}${From}${infer Suffix}`
? `${Prefix}${To}${ReplaceStrAll<Suffix, From, To>}`
: Str
// 'hello typescript typescript'
type f = ReplaceStrAll<'hello world world', 'world', 'typescript'>
SubString
type SubString<
Str extends string,
SubStr extends string
> = Str extends `${infer Prefix}${SubStr}${infer Suffix}`
? SubString<`${Prefix}${Suffix}`, SubStr>
: Str
// "hello_world"
type subString = SubString<'hello_world_t_t_t', '_t'>
Trim
// 去除所有空格
type Trim<str extends string> =
str extends `${infer Prefix}${
| ' '
| '\n'
| '\t'}${infer Suffix}`
? Trim<`${Prefix}${Suffix}`>
: str
// 'helloworld'
type g = Trim<' hello world '>
infer extends 和 & string
type TestInferLast<T extends string[]> =
T extends [ ...infer _Rest, infer Last ]
// 报错,不能将类型“Last”分配给类型“string | xxx
// 因为,infer推导的元素默认为unknown类型
? `last${Last}`
: never
通用解决方案
// 改 Last 调用处
`last${Last & string}`
// 或
Last extends string ? `last${Last}` : never
ts 4.7 开始进行了优化, 可以这样写 infer xxx extends string
// 改 Last 生成处
T extends [ ...infer _Rest, infer Last extends string ]
infer extends 对infer变量进行了类型转换
type StrToBoolean<Str> =
Str extends `${infer Bool extends boolean}` ? Bool : Str
// true
type res2 = StrToBoolean<'true'>
type StrToNull<Str> =
Str extends `${infer Null extends null}` ? Null : Str
// null
type res3 = StrToNull<'null'>
另外,如果extends 基础类型
,传入字面量会返回 字面量,而不是 基础类型
type StrToNum<Str> =
Str extends `${infer Num extends number}` ? Num : Str;
// 123, 而不是number
type testSingleStrToNum = StrToNum<'123'>
// 获取enum 的value类型
enum Code { a = 111, b = 222, c = 'abc' }
// "111" | "222" | "abc"
type testCode = `${Code}`
// 111 | 222 | "abc"
type testStrToNum = StrToNum<`${Code}`>
3. 函数 提取类型
type GetParameters<Func extends Function> = Func extends (
...args: infer Args
) => unknown
? Args
: never
// [a: string, b: number]
type h = GetParameters<(a: string, b: number) => void>
提取this类型
函数中对this 进行约束
class Dong {
name: string;
constructor() {
this.name = "dong";
}
hello() {
return 'hello, I\'m ' + this.name;
}
hello2(this: Dong) { // 约束 this 的类型为 Dong
return 'hello, I\'m ' + this.name;
}
}
const dong = new Dong();
// hello2 约束了this类型必须为dong,这里的this不会被计入参数列表中
dong.hello2();
// 报错。如果没有报错,说明没开启 strictBindCallApply 的编译选项
dong.hello2.call({something: 'lazy'});
// hello1 不报错
dong.hello.call({something: 'lazy'});
函数中提取 约束过的this类型
// 提取约束过的this的类型
type GetThisParameter<
Func extends (this: any, ...args: any[]) => any
> = Func extends ( this: infer ThisType, ...args: any[] ) => any
? ThisType
: never
type i = GetThisParameter<typeof dong.hello> // unknown
type j = GetThisParameter<typeof dong.hello2> // Dong
注意,不能直接 Dong.hello 访问hello,除非hello是静态属性
本质是因为 class 只是一个语法糖,只有在 new关键字执行时,hello才被创建并挂载进实例
Dong本身并不是变量值,也不是命名空间,仅仅是一个类型,所以不能Dong.hello
提取构造器类型
// 首先约束GetInstanceType<T>接收的T为构造器类型
type GetInstanceType<
ConstructorType extends new (...args: any) => any
> = ConstructorType extends new (
...args: any
) => infer InstanceType
? InstanceType
: any
interface Person { name: string }
interface PersonConstructor { new (name: string): Person }
// Person
type k = GetInstanceType<PersonConstructor>
4. 索引类型 提取类型
索引类型就是 键值对的对象类型(可通过T[K]访问)
// React中 PropsWithRef 就是这个原理,找到props中的ref属性的类型
type GetRefProps<Props> =
'ref' extends keyof Props
? Props extends { ref?: infer Value | undefined}
? Value
: never
: never;
'ref' extends keyof Props
的作用是:明确限制只有在 'ref' 存在时才进行下一步infer推断
Props extends { ref?: infer Value | undefined}
匹配并推断value类型
| undefined
作用是 从infer value 推断中额外排除了undefined,此时会是never
那就奇怪了,第二句 既做到了匹配ref 又做到了去除undefined,那第一句还有什么作用?
在 ts3.0 里面如果没有对应的索引,Obj[Key] 返回的是 {} 而不是 never,所以这样做下兼容处理。
所以第一句本质上只是一句兼容处理!!! 没有它也一样!!!
5. 一句话用法总结
使用 类型 extends 另一个类型 的模式判断是否匹配,
在匹配的同时 从另一个类型中把需要提取的部分 infer 提取变量,
后续可对此变量进行进一步处理