当你不想重复工作时,有时需要基于已有的类型创建另一种新类型。TypeScript 提供了几种实用工具类型,以方便开发人员进行常见的类型转换。这些工具类型在全局范围内可用,从源代码能看出他们很多是基于索引签名和映射类型的语法。
映射类型基于索引签名的语法,索引签名用于声明事先未声明的属性类型。
要看懂工具类型源代码,得了解一下索引签名。
索引签名
在 TypeScript 中,索引签名是一种用于定义对象的属性类型的方式。它允许我们按照特定的索引类型来访问对象的属性,并确定相应的属性值类型。
使用索引签名,我们可以定义对象的索引类型和对应的属性值类型。它有两种形式:字符串索引签名和数字索引签名。
字符串索引签名
字符串索引签名允许我们使用字符串来访问对象的属性,并确定该属性的值的类型。例如:
interface Person {
[key: string]: string;
}
const person: Person = {
name: "John",
age: "30",
};
const name: string = person["name"]; // 可以访问 name 属性并确定其为字符串类型
const age: string = person["age"]; // 可以访问 age 属性并确定其为字符串类型
const address: string = person["address"]; // 无法确定 address 属性的类型
数字索引签名
数字索引签名允许我们使用数字来访问对象的属性,并确定该属性的值的类型。例如:
interface Data {
[index: number]: string;
}
const data: Data = ["apple", "banana", "cherry"];
const fruit: string = data[0]; // 可以访问索引为 0 的元素,并确定其为字符串类型
const invalidValue: string = data[10]; // 无法确定索引为 10 的元素的类型
通过使用索引签名,我们可以灵活地定义对象的属性类型,使其适应不同的场景和需求。
Partial & Required & Readonly
- 源码
type Partial<T> = {
[P in keyof T]?: T[P]
}
type Required<T> = {
[P in keyof T]-?: T[P]
}
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
- 作用
- Partial:让
T
中的所有属性都是可选的 - Required:让
T
中的所有属性都是必需的 - Readonly:让
T
中的所有属性都是只读的
- 示例
interface User {
id: number
age?: number
name: string
}
// 相当于: type Optional = { id?: number | undefined; age?: number | undefined; name?: string | undefined; }
type PartialUser = Partial<User>
// 相当于: type RequiredUser = { id: number; age: number; name: string; }
type RequiredUser = Required<User>
// 相当于: type ReadonlyUser = { readonly id: number; readonly age: number | undefined; readonly name: string; }
type ReadonlyUser = Readonly<User>
Partial
和Readonly
类型分别对应映射时的两个附加修饰符:readonly
和?
。Required类型在可选修饰符?
加了前缀-
,表示移除可选性,换句话说就是必选的。
Pick & Omit
- 源码
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
- 作用
- Pick:从
T
中选择一组键属于联合类型K
的属性来构造类型。(加法) - Omit:使用除联合类型
K
以外的T
的属性来构造类型。(减法)
- 示例
interface User {
id: number
age?: number
name: string
}
// 相当于: type PickedUser = { id: number; age?: number | undefined; }
type PickedUser = Pick<User, 'id' | 'age'>
// 相当于: type OmittedUser = { age?: number | undefined; name: string; }
type OmittedUser = Omit<User, 'id'>
如果需要数据层级很深,我们可以通过结合条件类型,递归 Partial 类型来实现。
type DeepPartial<T> = T extends Function
? T
: T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T
type PartialObject = DeepPartial<object>
Exclude & Extract & NonNullable
- 源码
type Exclude<T, U> = T extends U ? never : T
type NonNullable<T> = T extends null | undefined ? never : T
type Extract<T, U> = T extends U ? T : never
- 作用
- Exclude:从
T
中排除那些可以分配给U
的类型来构造类型(差集) - NonNullable: 从
T
中排除null
和undefined
来构造类型 - Extract: 从
T
中提取可分配给U
的类型来构造类型(交集)
- 示例
type Coordinate = 'x' | 'y' | 'z' | undefined
// 相当于: type Excluded = 'z' | undefined
type Excluded = Exclude<Coordinate, 'x' | 'y'>
// 相当于: type NonNullabled = x' | 'y' | 'z'
type NonNullabled = NonNullable<Coordinate>
// 相当于: type Extracted = 'x' |' y'
type Extracted = Extract<Coordinate, 'x' | 'y'>
从语义上讲, Pick 相当于 Extract,Omit 相当于 Exclude。NonNullable 相当于 Exclude<Coordinate, undefined | null>。Pick 和 Omit 适用于对象、类和接口类型,Extract 和 Exclude 一般适用于联合和字面量类型。
Record
- 源码
type Record<K extends keyof any, T> = {
[P in K]: T
}
- 作用
以 K
中的每个属性作为 key
值,以 T
作为 value
构造一个 map
类型。
- 示例
type Row = 'dog' | 'cat'
interface Field {
name: string
age: number
}
// 相当于: type Pet = { dog: Field; cat: Field; }
type Pet = Record<Row, Field>
const pets: Pet = {
dog: {
name: 'wangcai',
age: 1
},
cat: {
name: 'bosimao',
age: 2
}
}
可以看到 Pet
类型是由 Record<Row, Field>
构造的。将 Row
中的每个值 ('dog' | 'cat'
) 与 Field
分组合并。Row
类型值作为 Pet
类型的键,Field
类型的值作为 Pet
类型的每个键对应的值。
Record 构造类型有点像数据库中的表结构,Row 表示行,Field 表示列。
评论 (0)