当你不想重复自己时,有时一种类型需要基于另一种类型。
🌐 When you don’t want to repeat yourself, sometimes a type needs to be based on another type.
映射类型建立在索引签名的语法之上,用于声明未提前声明的属性类型:
🌐 Mapped types build on the syntax for index signatures, which are used to declare the types of properties which have not been declared ahead of time:
tsTrytypeOnlyBoolsAndHorses = {[key : string]: boolean |Horse ;};constconforms :OnlyBoolsAndHorses = {del : true,rodney : false,};
映射类型是一种泛型类型,它使用 PropertyKey 的联合(通常通过 keyof 创建)来遍历键,从而创建一种类型:
🌐 A mapped type is a generic type which uses a union of PropertyKeys (frequently created via a keyof) to iterate through keys to create a type:
tsTrytypeOptionsFlags <Type > = {[Property in keyofType ]: boolean;};
在这个例子中,OptionsFlags 将获取类型 Type 的所有属性,并将它们的值更改为布尔类型。
🌐 In this example, OptionsFlags will take all the properties from the type Type and change their values to be a boolean.
tsTrytypeFeatures = {darkMode : () => void;newUserProfile : () => void;};typeFeatureOptions =OptionsFlags <Features >;
映射修饰符
🌐 Mapping Modifiers
在映射过程中可以应用两个额外的修饰符:readonly 和 ?,它们分别影响可变性和可选性。
🌐 There are two additional modifiers which can be applied during mapping: readonly and ? which affect mutability and optionality respectively.
你可以通过在前面加上 - 或 + 来删除或添加这些修饰符。如果你不添加前缀,则默认使用 +。
🌐 You can remove or add these modifiers by prefixing with - or +. If you don’t add a prefix, then + is assumed.
tsTry// Removes 'readonly' attributes from a type's propertiestypeCreateMutable <Type > = {-readonly [Property in keyofType ]:Type [Property ];};typeLockedAccount = {readonlyid : string;readonlyname : string;};typeUnlockedAccount =CreateMutable <LockedAccount >;
tsTry// Removes 'optional' attributes from a type's propertiestypeConcrete <Type > = {[Property in keyofType ]-?:Type [Property ];};typeMaybeUser = {id : string;name ?: string;age ?: number;};typeUser =Concrete <MaybeUser >;
通过 as 进行按键重新映射
🌐 Key Remapping via as
在 TypeScript 4.1 及更高版本中,你可以在映射类型中使用 as 子句重新映射键:
🌐 In TypeScript 4.1 and onwards, you can re-map keys in mapped types with an as clause in a mapped type:
tstype MappedTypeWithNewProperties<Type> = {[Properties in keyof Type as NewKeyType]: Type[Properties]}
你可以利用像 模板字面量类型 这样的功能,从已有的属性创建新的属性名:
🌐 You can leverage features like template literal types to create new property names from prior ones:
tsTrytypeGetters <Type > = {[Property in keyofType as `get${Capitalize <string &Property >}`]: () =>Type [Property ]};interfacePerson {name : string;age : number;location : string;}typeLazyPerson =Getters <Person >;
你可以通过条件类型生成 never 来过滤键:
🌐 You can filter out keys by producing never via a conditional type:
tsTry// Remove the 'kind' propertytypeRemoveKindField <Type > = {[Property in keyofType asExclude <Property , "kind">]:Type [Property ]};interfaceCircle {kind : "circle";radius : number;}typeKindlessCircle =RemoveKindField <Circle >;
你可以对任意联合类型进行映射,不仅仅是 string | number | symbol 的联合类型,而是任何类型的联合类型:
🌐 You can map over arbitrary unions, not just unions of string | number | symbol, but unions of any type:
tsTrytypeEventConfig <Events extends {kind : string }> = {[E inEvents asE ["kind"]]: (event :E ) => void;}typeSquareEvent = {kind : "square",x : number,y : number };typeCircleEvent = {kind : "circle",radius : number };typeConfig =EventConfig <SquareEvent |CircleEvent >
进一步探索
🌐 Further Exploration
映射类型与本类型操作部分的其他特性配合良好,例如这里有一个使用条件类型的映射类型,它会根据对象是否将属性 pii 设置为字面量 true 来返回 true 或 false:
🌐 Mapped types work well with other features in this type manipulation section, for example here is a mapped type using a conditional type which returns either a true or false depending on whether an object has the property pii set to the literal true:
tsTrytypeExtractPII <Type > = {[Property in keyofType ]:Type [Property ] extends {pii : true } ? true : false;};typeDBFields = {id : {format : "incrementing" };name : {type : string;pii : true };};typeObjectsNeedingGDPRDeletion =ExtractPII <DBFields >;