映射类型 (Mapped Types)
映射类型是 TypeScript 中一种强大的类型转换工具,它允许你基于现有类型创建新的类型。通过遍历一个类型的属性并对其应用某种变换,你可以轻松地创建出具有相似结构但不同属性类型的对象类型。映射类型常用于库设计、工具函数以及需要对已有类型进行细微调整的场景。
1. 基本概念
映射类型的基本语法如下:
type MappedType = {
[P in Keys]: Transformation;
};Keys是一个联合类型,表示要遍历的属性名。Transformation是对每个属性应用的类型转换表达式,可以包含属性P。
2. 使用场景
- 修改属性的可选性:将所有属性变为可选或必填。
- 改变属性的只读性:将所有属性变为只读或可写。
- 转换属性的类型:例如,将所有属性变为它们的包装类型(如
string变为String)。 - 添加修饰符:例如,为所有属性添加
readonly或?(可选)修饰符。
3. 映射类型中的 + 和 - 修饰符
在 TypeScript 的映射类型中,+ 和 - 修饰符用于控制属性的可选性(?)和只读性(readonly)。这些修饰符允许你在创建新类型时精确地调整属性的行为。通过添加或移除这些修饰符,你可以灵活地转换已有类型的属性特性。
1. 修饰符的作用
+修饰符:确保属性是必填或只读的。-修饰符:确保属性是可选或可写的。
2. 具体应用
2.1 控制属性的可选性
+?:使属性变为必填(移除可选性)。-?:使属性变为可选(添加可选性)。
2.2 控制属性的只读性
+readonly:使属性变为只读(添加只读性)。-readonly:使属性变为可写(移除只读性)。
3. 示例解析
示例 1: 控制属性的可选性
interface Person {
name: string;
age?: number;
}
// 将所有可选属性变为必填
type RequiredPerson = {
[P in keyof Person]-?: Person[P];
};
// 将所有必填属性变为可选
type OptionalPerson = {
[P in keyof Person]+?: Person[P];
};在这个例子中:
RequiredPerson将Person中的所有可选属性(如age)变为必填。OptionalPerson将Person中的所有必填属性(如name)变为可选。
示例 2: 控制属性的只读性
interface Person {
readonly name: string;
age: number;
}
// 将所有属性变为只读
type ReadonlyPerson = {
readonly [P in keyof Person]+?: Person[P];
};
// 将所有属性变为可写
type WritablePerson = {
-readonly [P in keyof Person]-?: Person[P];
};在这个例子中:
ReadonlyPerson将Person中的所有属性变为只读(包括原本就只读的name和原本可写的age)。WritablePerson将Person中的所有属性变为可写(包括原本只读的name和原本可写的age)。
4. 示例解析
示例 1: 修改属性的可选性
type PartialPerson = {
[P in keyof Person]?: Person[P];
};在这个例子中,PartialPerson 将 Person 类型的所有属性变为可选。
示例 2: 改变属性的只读性
type ReadonlyPerson = {
readonly [P in keyof Person]: Person[P];
};在这个例子中,ReadonlyPerson 将 Person 类型的所有属性变为只读。
示例 3: 转换属性的类型
type Stringify<T> = {
[P in keyof T]: string;
};
interface Person {
name: string;
age: number;
}
type StringifiedPerson = Stringify<Person>; // { name: string, age: string }在这个例子中,Stringify<Person> 将 Person 类型的所有属性值都转换为了 string 类型。
示例 4: 添加修饰符
type WithQuestionMark<T> = {
[P in keyof T]+?: T[P];
};
type WithReadOnly<T> = {
readonly [P in keyof T]: T[P];
};在这个例子中,WithQuestionMark 和 WithReadOnly 分别为所有属性添加了 ?(可选)和 readonly 修饰符。
5. 内置映射类型
TypeScript 提供了一些常用的内置映射类型,这些类型可以帮助简化常见的类型操作:
Partial<T>:使T的所有属性变为可选。Readonly<T>:使T的所有属性变为只读。Record<K, T>:构建一个新的对象类型,其中键来自K,值来自T。Pick<T, K>:从T中挑选出由K指定的属性。Exclude<T, U>:从T中排除所有可以赋值给U的类型。Extract<T, U>:从T中提取出所有可以赋值给U的类型。
示例:使用内置映射类型
interface Person {
name: string;
age: number;
}
type PartialPerson = Partial<Person>; // { name?: string; age?: number; }
type ReadonlyPerson = Readonly<Person>; // { readonly name: string; readonly age: number; }
type StringKeyedObject = Record<string, any>; // { [key: string]: any; }
type NameOnly = Pick<Person, "name">; // { name: string; }6. 条件类型与映射类型的结合
你可以将条件类型与映射类型结合使用,以实现更复杂的类型转换逻辑。
示例:根据属性值类型有条件地转换属性
type TransformProps<T> = {
[P in keyof T]: T[P] extends string ? number : T[P];
};
interface Person {
name: string;
age: number;
}
type TransformedPerson = TransformProps<Person>; // { name: number; age: number; }在这个例子中,TransformProps<Person> 将 Person 类型中的所有 string 类型属性转换为 number 类型。
7. 注意事项
类型安全:映射类型提供了强大的类型转换能力,但在使用时应确保不会破坏类型的安全性。
性能考虑:复杂类型的映射可能会增加编译时间,对于大型项目应权衡其利弊。
意图清晰:在设计映射类型时,尽量保持意图清晰,避免过度复杂的类型操作,提高代码的可读性和维护性。
总结
映射类型是 TypeScript 中一个非常有用的特性,它使得你可以基于现有类型创建新的类型,并对属性进行各种转换。通过合理使用映射类型,你可以编写出更加灵活且类型安全的代码。