内置工具类型
1. 工具类型的分类
内置的工具类型按照类型操作的不同,可以大致划分为这么几类:
信息
- 对属性的修饰,包括对象属性和数组元素的可选/必选、只读/可写。我们将这一类统称为属性修饰工具类型。
- 对既有类型的裁剪、拼接、转换等,比如使用对一个对象类型裁剪得到一个新的对象类型,将联合类型结构转换到交叉类型结构。我们将这一类统称为结构工具类型。
- 对集合(即联合类型)的处理,即交集、并集、差集、补集。我们将这一类统称为集合工具类型。
- 基于 infer 的模式匹配,即对一个既有类型特定位置类型的提取,比如提取函数类型签名中的返回值类型。我们将其统称为模式匹配工具类型
- 模板字符串专属的工具类型,比如神奇地将一个对象类型中的所有属性名转换为大驼峰的形式。这一类当然就统称为模板字符串工具类型。
2. 属性修饰工具类型
1. Partial
Partial<Type>
用于构造一个Type
下面的所有属性都设置为可选的类型,这个工具类型会返回代表给定的一个类型的子集的类型。
interface IPerson {
name?: string
age?: number
}
type p = Partial<IPerson>
/*
type p = {
name?: string | undefined;
age?: number | undefined;
}
*/
Partial实现
/**
* in 操作符遍历传入类型的键所组成的联合类型(keyof 的返回类型),添加可选符号依次返回
*/
type myPartial<T> = {
[k in keyof T]?: T[k]
}
2. Required
Required<Type>
用于构造一个Type
下面的所有属性全都设置为必填的类型,这个工具类型跟Partial
相反。
interface Person {
name?: string
age?: number
}
type p = Required<Person>
/*
type p = {
name: string;
age: number;
}
*/
Required实现
/**
* 遍历键联合类型,依次删除可选操作符 返回当前键值的类型
*/
type myRequired<T> = {
[k in keyof T]-?: T[k]
}
3. ReadOnly
ReadOnly<Type>
用于构造一个Type
下面的所有属性全都设置为只读的类型,意味着这个类型的所有的属性全都不可以重新赋值。
interface Person {
name: string
age: number
}
type p = Readonly<Person>
/*
type p = {
readonly name: string;
readonly age: number;
}
*/
ReadOnly实现
/**
* 遍历键联合类型,依次添加只读操作符
*/
type myReadOnly<T> = {
readonly [k in keyof T]: T[k]
}
4. Mutable(自定义)
typeScript
没有内置将类型只读属性全部转换为普通属性的方法,这里可以自定义一个:Mutable<Type>
用于构造一个Type
下面的所有只读属性全都设置为可修改的类型,意味着这个类型的所有的属性全都可以重新赋值。 实现:
/**
* 遍历泛型键的联合类型 去除readonly修饰符
*/
type Mutable<T> = {
-readonly [k in keyof T]: T[k]
}
示例:
interface IPerson1 {
readonly name: string
readonly age: number
}
type Mutable<T> = {
-readonly [k in keyof T]: T[k]
}
type m = Mutable<IPerson1>
/*
type m = {
name: string;
age: number;
}
*/
5. 深度添加修饰符(自定义)
深度添加修饰符:通过将类型进行递归来深度操作对象类型的属性
/* 对象类型深度可选 */
type DeepPartial<T extends object> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
}
/* 深度必须属性 */
type DeepRequired<T extends object> = {
[K in keyof T]-?: T[K] extends object ? DeepRequired<T[K]> : T[K];
}
/* 深度只读 */
type DeepReadonly<T extends object> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
}
/* 深度可写 */
type DeepMutable<T extends object> = {
-readonly [K in keyof T]: T[K] extends object ? DeepMutable<T[K]> : T[K];
}
3. 结构工具类型
1. Record
Record<keys , Type>
用于构造一个对象类型,它所有的key
(键)都是Keys
类型,它所有的value
(值)都是Type
类型。这个工具类型可以被用于映射一个类型的属性到另一个类型。
interface CatInfo {
age: number
breed: string
}
type CatName = 'miffy' | 'boris' | 'mordred'
const cats: Record<CatName, CatInfo> = {
miffy: { age: 10, breed: 'Persian' },
boris: { age: 5, breed: 'Maine Coon' },
mordred: { age: 16, breed: 'British Shorthair' },
}
/* 一个任意键值的对象类型 ,效果类似索引签名 */
type c = Record<string, any>
/*
type c = {
[key:string]:any
}
*/
Record实现
/**
* string | number | symbol 就是js中对象键所拥有的类型
*/
type Record<K extends string | number | symbol, T> = {
[P in K]: T
}
注意
Record
实现中,传入的类型参数 K
被限制为string | number | symbol
(js对象键的类型),而上例的代码中,传入的K
的类型为:"miffy" | "boris" | "mordred"
(字面量类型所组成的联合类型),从层级链可知:
相同原始类型字面量所组成的联合类型 < 该原始类型
"miffy" | "boris" | "mordred"
<string
原始类型 < 包含该原始类型所组成的联合类型
string
<'string' | 'number' | 'boolean'
所以
"miffy" | "boris" | "mordred"
类型是'string' | 'number' | 'boolean'
的子类型
2. Pick
Pick<Type, Keys>
用于构造一个类型,它是从Type
类型里面挑了一些属性Keys
(Keys
是字符串字面量 或者 字符串字面量的联合类型),其中Keys
受到keyof Type
约束
interface IPerson {
name: string
age: number
hobby: string[]
}
type a = Pick<IPerson, 'age'>
/*
type a = {
age: number;
}
*/
type p = Pick<IPerson, 'age' | 'name'>
/*
type p = {
age: number;
name: string;
}
*/
Pick实现
type Pick<T, K extends keyof T> = {
[p in K]: T[p]
}
3. Omit
Omit<Type, Keys>
用于构造一个类型,它是从Type
类型里面过滤了一些属性Keys
(Keys
是字符串字面量 或者 字符串字面量的联合类型),其中Keys
不受keyof Type
约束
interface IPerson {
name: string
age: number
hobby: string[]
}
type p = Omit<IPerson, 'hobby'>
/*
type p = {
name: string;
age: number;
}
*/
type a = Omit<IPerson, 'hobby' | 'name'>
/*
type a = {
age: number;
}
*/
Omit实现
/**
* 借助Pick Exclude
* Exclude: 从联合类型中剔除指定的类型
* type p = string | number | boolean | null
* type s = Exclude<p , null | boolean> // string | number
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
/* 借助Exclude */
type Omit<T, K extends keyof string | number | symbol> = {
[p in Exclude<keyof T, K>]: T[p]
}
4. 集合工具类型
集合工具类型主要使用条件类型、条件类型分布式特性实现。
1. Extract(交集)
Extract<Type , Union>
用于构造一个类型,它是从Type
类型里面提取了所有可以赋给Union
的类型。
type a = 1 | 2 | 3
type b = 2 | 3 | 5
type x = Extract<a, b> // 2 | 3
/* 根据分布式条件特性, 以上代码相当于: */
type x =
(1 extends 2 | 3 | 5 ? 1 : never) // never
| (2 extends 2 | 3 | 5 ? 2 : never) // 2
| (3 extends 2 | 3 | 5 ? 3 : never) // 3
Extract实现
/**
* 泛型参数T为联合类型的时候,分布式条件特性起作用,拆分T依次执行条件类型,
* 最终结果组成新的联合类型返回
*/
type Extract<T, U> = T extends U ? T : never
2. Exclude(差集)
Exclude<UnionType, ExcludedMembers>
用于构造一个类型,它是从UnionType
联合类型里面排除了所有可以赋给ExcludedMembers
的类型。
type unioType = 1 | 2 | 3
// type c = 1 | 2
type c = Exclude<unioType, 3 | 5>
/*根据分布式条件特性, 以上代码相当于: */
type c =
(1 extends 3 | 5 ? never : 1) // 1
| (2 extends 3 | 5 ? never : 2) // 2
| (3 extends 3 | 5 ? never | 3) //never
Exclude实现
// 与Extract相反
type Exclude<T, U> = T extends U ? never : T
3. NonNullable
NonNullable<Type>
用于构造一个类型,这个类型从Type
中排除了所有的null、undefined
的类型。
type Res = NonNullable<null | undefined | string | { name: string }>
/*
type Res = string | {
name: string;
}
*/
NonNullable实现
type NonNullable<T> = T & {}
type NonNullable<T> = Exclude<T, undefined | null>
5. 模式匹配工具类型
模式匹配工具类型主要使用条件类型 与 infer 关键字实现。
1. Parameters
Parameters<Type>
用于根据所有Type
中函数类型的参数构造一个元祖类型。
type Fun = (a: string, b: number) => void
// type c = [a: string, b: number]
type c = Parameters<Fun>
Parameters实现
type FuncType = (...args: any[]) => any
/* infer 推断args剩余参数类型返回 */
type Parameters<T extends FuncType> = T extends (...args: infer P) => any ? P : never
2. ReturnType
ReturnType<Type>
用于构造一个含有Type
函数的返回值的类型。
type Fun = (a: string, b: number) => string
// string
type x = ReturnType<Fun>
ReturnType实现
type FuncType = (...args: any[]) => any
/* infer 推断返回值类型返回 */
type ReturnType<T extends FuncType> = T extends (...args: any) => infer R ? R : any
6. 基于已知属性进行部分修饰
内置的工具类型
Partial
,ReadOnly
,Required
,包括之前的自定义深度操作都是全量取操作对象类型属性,可以实现指定属性名来添加访问符:
/* 测试对象类型 */
interface IPerson {
name: string
age: number
hobby: string[]
code: {
lang: string
tool: string
}
}
提示
- 根据传入属性拆分对象类型(使用结构工具类型
Pick
和Omit
),添加或去除需要的访问符 - 合并拆分的两个对象类型
1. 自定义Partial部分可选
/* 指定部分属性可选 */
type MarkPropsAsOptional<
T extends object,
K extends keyof T = keyof T
> = Partial<Pick<T, K>> & Omit<T, K>
// 让IPerson接口中的code 和 hobby属性可选
type Res = MarkPropsAsOptional<IPerson, 'hobby' | 'code'>
/*
type res = Partial<Pick<IPerson, "hobby" | "code">> & Omit<IPerson, "hobby" | "code">
*/
T
为需要处理的对象类型,而K
为需要标记为可选的属性。由于此时K
必须为T
内部的属性,因此我们将其约束为keyof T
,即对象属性组成的字面量联合类型。同时为了让它能够直接代替掉Partial
,我们为其指定默认值也为keyof T
,这样在不传入第二个泛型参数时,它的表现就和Partial
一致,即全量的属性可选。而其组成中,Partial<Pick<T, K>>
为需要标记为可选的属性组成的对象子结构,Omit<T, K>
则为不需要处理的部分,使用交叉类型将其组合即可。
鼠标移入Res
,类型结果没有展平,可以再实现一个展平类型的工具类型(将类型复制一下就行):
type Flatten<T> = { [k in keyof T]: T[k] }
改造 MarkPropsAsOptional
:
type MarkPropsAsOptional<
T extends object,
K extends keyof T = keyof T
> = Flatten<Partial<Pick<T, K>> & Omit<T, K>> // 最后借助Flatten<T>展平
type Res = MarkPropsAsOptional<IPerson, 'code' | 'hobby'>
/*
type res = {
hobby?: string[] | undefined;
code?: {
lang: string;
tool: string;
} | undefined;
name: string;
age: number;
}
*/
2. 其他部分修饰
- 指定部分属性必须
type MarkPropsAsRequired<
T extends object,
K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Required<Pick<T, K>>>
- 指定部分属性只读
export type MarkPropsAsReadonly<
T extends object,
K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Readonly<Pick<T, K>>>
- 指定部分属性去除只读
Mutable
为自定义类型, 内部实现 :
type Mutable<T> = {-readonly [k in keyof T]:T[k]}
type MarkPropsAsMutable<
T extends object,
K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Mutable<Pick<T, K>>>