- TS 高级技巧:范型反向推导、索引签名、条件类型、keyof、infer
- 实现 Ref 类型
- 源码
# TS 高级技巧
# 范型反向推导
// 正向推导
type value<T> = T;
type numberVal = value<number> // number
type stringVal = value<string> // string
// 反向推导
declare function add<T> (val: T): T
let n:string;
add(n) // add<string> (val: string): string
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 索引签名
type Person = {
name: string
age: number
};
type personAge = Person['age'] // number
1
2
3
4
5
6
2
3
4
5
6
# 条件类型
type isString<T> = T extends string ? true : false
type A = isString<'1'> // true
type typeName<T> = T extends string
? 'string'
: T extends number
? 'number'
: 'object';
type B = typeName<'1'> // 'string'
type C = typeName<2> // 'number'
type D = typeName<true> // 'object'
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# keyof
type Keys = keyof Person // 'name | age'
type copyPerson = { // 拷贝 Person 的类型
[k in keyof Person]: Person[k]
}
type partialPerson = { // 实现 Partial<Person>
[k in keyof Person]?: Person[k]
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# infer
只能用在条件类型中
type Flatten<T> = T extends Array<infer R> ? R : T; // 获取数组成员类型
type Unpromisify<T> = T extends Promise<infer R> ? R : T; // 获取 Promise 的值
type ParamType<T> = T extends (param: infer P) => any ? P : T; // 获取函数参数类型
type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any; // 获取函数返回值类型
1
2
3
4
2
3
4
# 实现 Ref 类型
# Ref
类型定义为:
type Ref<T> = {
value: T
}
1
2
3
2
3
# ref
函数定义为:
declare function ref<T>(value: T): Ref<T>
1
用例:
ref(2) // Ref<number>
ref(ref(ref(ref(ref(2))))) // Ref<number>
ref({ a: ref('1'), b: ref(2) }) // Ref<{ a: string, b: number }>
1
2
3
4
5
2
3
4
5
# UnwrapRef
如果 T
是 object
类型且内部字段也有 Ref
类型,也会被解包,所以再被 Ref
包裹前对内部进行解包:
declare function ref<T>(value: T): Ref<UnwrapRef<T>> // 解包 T
1
目标为实现这个 UnwrapRef
类型。
使用 infer 进行一层解包:
type UnwrapRef<T> = T extends Ref<infer R> ? R : T
UnwrapRef<Ref<number>> // number
1
2
3
2
3
如果 R
还是 Ref
类型则递归使用 UnwrapRef
进行解包:
// ❌ Type alias 'UnwrapRef' circularly references itself.ts(2456)
type UnwrapRef<T> = T extends Ref<infer R>
? UnwrapRef<R>
: T
1
2
3
4
2
3
4
报错了,不允许循环引用自己。
可以使用笨方法,定义多个 UnwrapRef
:
type UnwrapRef<T> = T extends Ref<infer R>
? UnwrapRef2<R>
: T
type UnwrapRef2<T> = T extends Ref<infer R>
? UnwrapRef3<R>
: T
type UnwrapRef3<T> = T extends Ref<infer R>
? UnwrapRef4<R>
: T
...
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
这样会写很多同样的代码,代码不够优雅,也无法覆盖所有的嵌套情况,因为无法确定用户会嵌套多少层。
# 递归 UnwrapRef
但是对象的属性类型引用对象自身的类型在 TS 中是可以的:
// ✅
type Person = {
name: string
age: number
child: Person
}
1
2
3
4
5
6
2
3
4
5
6
所以我们可以使用对象不同的键来对应不同的解包逻辑,使用条件类型加索引签名决定逻辑分支,重写 UnwrapRef
:
type UnwrapRef<T> = {
ref: T extends Ref<infer R> ? UnwrapRef<R> : T,
other: T
}[T extends Ref<any> ? 'ref' : 'other']
1
2
3
4
2
3
4
# 支持对象
新增 object
类型的解包逻辑分支:
type UnwrapRef<T> = {
ref: T extends Ref<infer R> ? UnwrapRef<R> : T,
object: object: { [k in keyof T]: UnwrapRef<T[k]>},
other: T,
}[T extends Ref<any>
? 'ref'
: T extends object
? 'object'
: 'other']
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
后续要新增其它的逻辑分支(如数组,计算属性等)也会变得很容易。
# 完整代码
type Ref<T> = {
value: T
}
type UnwrapRef<T> = {
ref: T extends Ref<infer R> ? UnwrapRef<R> : T,
object: object: { [k in keyof T]: UnwrapRef<T[k]>},
other: T,
}[T extends Ref<any>
? 'ref'
: T extends object
? 'object'
: 'other']
declare function ref<T>(value: T): T extends Ref<any> ? T : Ref<UnwrapRef<T>> // 此处优化:如果 T 已经是 Ref 类型,则不需要解包,直接返回即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15