在 TypeScript 中,映射类型(Mapped Types)是一种通过遍历一个类型的属性并对其进行修改、扩展或生成新类型的强大工具。通过映射类型,你可以基于现有类型动态创建新的类型。
映射类型的基础语法利用了 TypeScript 中的 in
关键字,允许你对类型中的每个属性进行处理和转换。映射类型使你能够灵活地操作对象类型的结构,控制对象的每个属性如何被类型化。
基本语法
映射类型的基本语法如下:
type MappedType<T> = {
[P in keyof T]: Type;
};
在这个基础语法中:
T
:是我们要遍历的类型。keyof T
:表示类型T
中所有键的联合类型。keyof T
获取T
的所有属性名,并将其作为键来进行映射。[P in keyof T]
:表示遍历T
中的每个键P
。Type
:是每个键对应的新类型。
基本的映射类型
假设我们有一个接口 Person
,我们想要遍历这个类型的所有属性,并将所有属性的类型都设为 string
。
interface Person {
name: string;
age: number;
}
type StringifiedPerson = {
[P in keyof Person]: string;
};
在上面的代码中:
-
keyof Person
会获取Person
类型中的所有键:"name"
和"age"
。 -
StringifiedPerson
类型的每个键对应的值都被映射为string
类型。所以最终的类型StringifiedPerson
会变成如下图结果所示。
映射类型与可选属性
映射类型还允许我们更灵活地控制属性的可选性、只读性等。你可以使用 ?
或 readonly
来修改属性的特性。
使所有属性变为可选
我们可以使用映射类型和 ?
操作符来使所有属性变为可选:
type OptionalPerson = {
[P in keyof Person]?: Person[P];
};
在上面的代码中:
[P in keyof Person]
会遍历Person
类型的所有属性。?
标记了每个属性为可选属性。Person[P]
保证了每个属性的值类型保持一致。
OptionalPerson
会变成:
使所有属性变为只读
如果我们希望将对象的所有属性变成只读属性,可以使用 readonly
:
type ReadonlyPerson = {
readonly [P in keyof Person]: Person[P];
};
在上面的代码中readonly
修饰符使每个属性成为只读属性。
ReadonlyPerson
类型会变成:
映射类型的条件类型
映射类型还可以与 条件类型 结合使用,创建更加动态和复杂的类型映射。比如,如果你想将所有 number
类型的属性转成 string
,而不改变其他类型的属性,你可以使用条件类型来判断每个属性的类型。
type TransformNumbers<T> = {
[P in keyof T]: T[P] extends number ? string : T[P];
};
在这个例子中:
-
[P in keyof T]
会遍历T
类型的每个属性。 -
T[P] extends number ? string : T[P]
是一个条件类型,用来判断属性值类型是否为number
,如果是,转换为string
,否则保持原类型。
应用到 Person
类型
假设我们有以下的 Person
类型:
interface Person {
name: string;
age: number;
isActive: boolean;
}
使用 TransformNumbers
类型:
type TransformedPerson = TransformNumbers<Person>;
TransformedPerson
会变成:
这里,age
的类型被转换成了 string
,而其他属性(name
和 isActive
)则保持不变。
使用 as
操作符修改键
有时我们需要在映射类型中修改属性的名字,可以通过 as
操作符来实现。例如,将所有属性的键都加上一个前缀:
type PrefixedPerson = {
[P in keyof Person as `prefix_${string & P}`]: Person[P];
};
在上面的代码中:
-
as
操作符允许我们通过模板字面量类型修改每个键的名称。 -
string & P
确保P
是一个字符串。
如下代码所示:
interface Person {
name: string;
age: number;
isActive: boolean;
}
type PrefixedPerson = {
[P in keyof Person as `prefix_${string & P}`]: Person[P];
};
const person: PrefixedPerson = {
prefix_name: 'Moment', // 对应原属性 name
prefix_age: 30, // 对应原属性 age
isActive: true, // 类型错误:对象字面量只能指定已知属性,并且“isActive”不在类型“PrefixedPerson”中。
};
这会将所有属性的名称加上 prefix_
,例如:
映射类型与 in
的更多用法
in
关键字在映射类型中的作用是遍历 keyof T
中的每个键来创建新的类型。可以通过以下方式对它进行更复杂的操作:
-
只选择特定的键:通过条件类型或其他类型操作,来决定哪些键需要被映射或修改。
-
生成新类型:你可以通过映射类型来生成基于现有类型的全新类型,如创建只包含某些属性的对象类型。
如下代码所示:
type PickNumberProperties<T> = {
[P in keyof T]: T[P] extends number ? T[P] : never;
};
在这个例子中,PickNumberProperties
类型会只保留那些值类型是 number
的属性,其他属性则会变为 never
,即排除掉。
type PickedPerson = PickNumberProperties<Person>;
// PickedPerson 的类型是 { age: number; isActive: never }
如下图所示:
总结
映射类型是 TypeScript 中的一种强大工具,允许我们通过遍历现有类型的属性并修改或生成新类型。它通过 in
和 keyof
关键字,可以动态地对对象类型的每个属性进行转换,支持修改属性的类型、添加修饰符(如 readonly
、optional
)等操作。映射类型还支持与条件类型结合使用,从而实现更复杂的转换逻辑。此外,TypeScript 提供了内置的映射类型工具,如 Partial
、Readonly
、Required
和 Record
,帮助开发者高效处理对象类型。