TS类型体操(二) TS内置工具类1
TS类型体操(二) TS内置工具类1
上一篇是基础知识,虽然是基础知识,但非常重要,没看的建议先看上一篇。
从这一篇开始,我们来研究一下TS内置的工具类,看看类型体操具体是怎么玩的。
如果对本篇内容有疑问,或发现有错误,欢迎批评指正!!
Exclude<T, U>
——求补集
TS内置工具类Exclude<T, U>
可以将联合类型的一部分排除,从集合的角度来看,相当于求补集:
type A = string | undefined
type B = Exclude<A, undefined>
//--------------------------------------------
// 结果:
type B = string
Exclude
实现非常简单:
type Exclude<T, U> = T extends U ? never : T
还记得我在上一篇中讲到的联合类型分发(distributive conditional types
)吗?
这里其实就用到了这个机制,以上面的例子来说,它的计算过程是这样的:
type A = string | undefined
type B = Exclude<A, undefined>
// 相当于↓
type B = string extends undefined ? never : string | undefined extends undefined ? never : undefined
// 结果↓
type B = string | never
// 结果↓
type B = string
NonNullable<T>
——排除空值
TS内置工具类NonNullable<T>
可以将类型中的空值(null
和 undefined
)排除,例如:
type A = string | undefined | null
type B = NonNullable<A>
//--------------------------------------------
// 结果
type B = string
看到这里,聪明的你也许会觉得,它的实现应该是借助了Exclude:
type MyNonNullable<T> = Exclude<T, undefined | null>
是的,从效果来看,这么做和NonNullable
是一模一样的,但事实上,TS却用了个更简单的做法:
type NonNullable<T> = T & {}
乍一看你也许会满脑子问号——这啥?
这里的{}
,其实它并不是空对象,而是表示一个特殊的非空类型。
特殊的非空类型:{}
上一篇文章我提到过,从集合的角度来看,unknown
相当于全集,其他所有类型都是unknown
的子集,而{}
则是范围仅次于unknown
的特殊类型,{}
加上null
、再加上undefined
,就等于unknown
。
我们可以这样验证:
type T = unknown extends {} | undefined | null ? true : false // true
明白了这个,我们就知道为什么T & {}
能排除空值了——因为 {}
是除空值以外其他所有类型的父集。
type t1 = string extends {} ? true : false // ture
type t2 = number extends {} ? true : false // ture
type t3 = [] extends {} ? true : false // ture
type t4 = true extends {} ? true : false // ture
type t5 = object extends {} ? true : false // ture
type t6 = undefined extends {} ? true : false // false
type t7 = null extends {} ? true : false // false
映射类型
TS内置工具类 Record
Partial
Required
Readonly
Pick
Omit
,都是通过映射类型来实现的,本节让我们看看什么是映射类型。
获取对象类型某个字段的类型
在介绍映射类型前,容我补充一个知识点——获取对象类属性的类型
TS类型不能向JS那样用点.
来获取属性,但是可以使用中括号,例如:
元组
type T = [number, boolean, string]
type T1 = T[1] // boolean - 获取数组某一项的类型
type Len = T['length'] // 3 - 获取数组的长度
type All = T[number] // number | boolean | string - 相当于将元组类型转换为联合类型
对象
type P = {
x: 1
y: 8
}
type X = P['x'] // 1
type Key = P[keyof P] // 1 | 8
type Key = 'name' | 'age'
type User = {
name: string
age: number
}
type Value = User[Key] // type Value = string | number
基本使用
type K = 'name' | 'age'
type R = {
[P in K]: P
}
//--------------------------------------------
// 结果:
type R = {
name: 'name'
age: 'age'
}
可以看到这里使用的in
操作符,它可以将一个联合类型映射为对象类型的属性,P in K
,其中K
必须是联合类型string | number | symbol
的子集。
使用映射类型复制一个对象类型:
type A = {
0: boolean
a: string
b: number
}
type T = {
[P in keyof A]: A[P]
}
//--------------------------------------------
// 结果
type T = {
0: boolean
a: string
b: number
}
当然,我们完全没必要用这种方式来复制一个对象类型,这里只是为了举例说明in
的使用方法以及它的作用。
映射类型使用断言
在in
的子句后面可以添加as
断言,例如上面复制的例子,我们可以给它添加断言:
type A = {
0: boolean
a: string
b: number
}
type T = {
[P in keyof A as string]: A[P] // 断言为string
}
//--------------------------------------------
// 结果
type T = {
[x: string]: string | number | boolean
}
在这个例子中,断言将类型扩大了,原本keyof A
应该是 0 | 'a' | 'b'
,断言后扩大成了string
。
我们再看这个例子:
type A = {
0: boolean
a: string
b: number
}
type T = {
[P in keyof A as P extends string ? P : never]: A[P]
}
//--------------------------------------------
// 结果
type T = {
a: string
b: number
}
在这个例子中,P extends string ? P : never
的意思是:如果P是string,就保留P这个属性,否则就不要这个属性(never)。于是,结果就只剩a
和b
两个属性了。
断言可以扩大类型,也可以缩小类型,我们甚至可以断言原本不存在的属性:
type A = {
0: boolean
a: string
b: number
}
type T = {
[P in keyof A as 'c']: A[P]
}
//--------------------------------------------
// 结果
type T = {
c: string | number | boolean
}
可以看到,keyof A
原本并没有c
这个属性,断言凭空指定了它,只要它是 string | number | symbol
的子集 即可。
映射类型实现的TS内置工具类
下面我们来看一看通过映射类型实现的TS内置工具类。
其实,只要你弄懂了映射类型的用法,这些工具类的实现都是很简单的。
Partial
Partial
会将对象类型的所有属性都变为可选属性
type Partial<T> = {
[P in keyof T]?: T[P] | undefined
}
复制 + 问号
Required
Required
会将对象类型的所有属性都变为必须属性
type Required<T> = {
[P in keyof T]-?: T[P]
}
复制-问号
Readonly
Readonly
会将对象类型的所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
复制并只读
Record
Record
的作用是创建一个指定键、值类型的对象类型。
type List = Record<'a' | 'b' | 'c', string>
//--------------------------------------------
// 结果:
type List = {
a: string
b: string
c: string
}
Record
的实现:
type Record<K extends string | number | symbol, T> = {
[P in K]: T
}
由于K
必须是string | number | symbol
的子集,所以必须有泛型约束,否则会报错:
type MyRecord<K, T> = {
[P in K]: T // 类型错误:不能将类型“K”分配给类型“string | number | symbol”。ts(2322)
}
Pick
Pick
用于提取对象中指定的属性,得到一个新的对象类型。
type T = Pick<{ a: 1; b: 2; c: 3 }, 'a' | 'c'>
//--------------------------------------------
// 结果:
type T = { a: 1; c: 3 }
它的实现十分简单:
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
Omit
Omit
与Pick
的作用正好相反,它用于排除对象类型的指定属性。
type T = Omit<{ a: 1; b: 2; c: 3 }, 'a' | 'c'>
//--------------------------------------------
// 结果:
type T = { b: 2 }
它的实现需要借助Exclude
:
type Omit<T, K extends string | number | symbol> = {
[P in Exclude<keyof T, K>]: T[P]
}
结束
本来打算再多介绍几个工具类,但涉及的知识点有点多,全塞一篇文章里有点太多了,ReturnType
Parameters
等等,这些就留到下一篇吧。
转载自:https://juejin.cn/post/7229313753950568506