如何用TypeScript映射类型实现带readonly和可选属性且保留顺序?
TypeScript映射类型优化:根据定义自动应用readonly和可选属性
我需要在TypeScript中通过映射类型,根据初始类型定义自动为属性添加readonly和可选标记。先定义基础类型:
type PropertyAttributes = { readonly optional?: boolean; readonly readonly?: boolean; readonly value: string; // 示例简化,实际类型可能不同 }; type ObjectDefinition = Record<string, PropertyAttributes>;
目前我通过拆分四种不同类型再合并的方式实现需求,但代码冗长,且合并后无法保留原属性顺序:
// 对应四种属性组合的类型 type ReadonlyRequired<O extends ObjectDefinition> = { +readonly [K in keyof O as O[K]["readonly"] extends true ? O[K]["optional"] extends true ? never : K : K]: O[K]; }; type ReadonlyOptional<O extends ObjectDefinition> = { +readonly [K in keyof O as O[K]["readonly"] extends true ? O[K]["optional"] extends true ? K : never : never]?: O[K] | undefined; }; type MutableRequired<O extends ObjectDefinition> = { -readonly [K in keyof O as O[K]["readonly"] extends true ? never : O[K]["optional"] extends true ? never : K]: O[K]; }; type MutableOptional<O extends ObjectDefinition> = { -readonly [K in keyof O as O[K]["readonly"] extends true ? never : O[K]["optional"] extends true ? K : never]?: O[K] | undefined; }; // 展开工具类型 export type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never; // 合并所有类型 type Combine<O extends ObjectDefinition> = Expand< ReadonlyRequired<O> & ReadonlyOptional<O> & MutableRequired<O> & MutableOptional<O> >;
还有一个用于映射值的类型:
// 映射到value属性,保留键名 type MapToValue<O extends ObjectDefinition> = {[K in keyof O]: NonNullable<O[K]>["value"]}; // 用NonNullable避免可选类型导致的unknown type ToObj<O extends ObjectDefinition> = MapToValue<Combine<O>>;
这个方案能正常工作,但代码太长,且属性顺序无法保留。想知道有没有更简洁的实现方式,或者可优化的细节。
优化方案:单映射类型实现
可以通过单个映射类型直接处理每个属性的readonly和可选标记,这样既简化代码,又能保留原属性顺序(因为同态映射会维持原键的顺序):
核心实现
// 直接根据每个属性的定义生成对应修饰符 type DefineObject<O extends ObjectDefinition> = { // 条件添加readonly:readonly为true时添加+readonly,否则-readonly [K in keyof O as K]: O[K]["readonly"] extends true ? readonly (O[K]["optional"] extends true ? O[K] | undefined : O[K]) : (O[K]["optional"] extends true ? O[K] | undefined : O[K]); }; // 优化后的ToObj:直接映射到value并应用修饰符 type ToObj<O extends ObjectDefinition> = { [K in keyof O as K]: O[K]["readonly"] extends true ? readonly (O[K]["optional"] extends true ? NonNullable<O[K]>["value"] | undefined : NonNullable<O[K]>["value"]) : (O[K]["optional"] extends true ? NonNullable<O[K]>["value"] | undefined : NonNullable<O[K]>["value"]); };
更简洁的版本(复用逻辑)
提取判断逻辑为单独工具类型,进一步提升可读性和复用性:
// 判断属性是否为可选 type IsOptional<T extends PropertyAttributes> = T["optional"] extends true ? true : false; // 判断属性是否为只读 type IsReadonly<T extends PropertyAttributes> = T["readonly"] extends true ? true : false; // 生成单个属性的最终类型(含修饰符) type PropertyType<T extends PropertyAttributes> = IsReadonly<T> extends true ? readonly (IsOptional<T> extends true ? NonNullable<T>["value"] | undefined : NonNullable<T>["value"]) : (IsOptional<T> extends true ? NonNullable<T>["value"] | undefined : NonNullable<T>["value"]); // 最终目标类型 type ToObj<O extends ObjectDefinition> = { [K in keyof O]: PropertyType<O[K]>; };
优化优势
- 代码精简:无需拆分多类型再合并,逻辑集中在单个映射中
- 保留属性顺序:同态映射维持
ObjectDefinition中属性的原始顺序 - 可读性提升:逻辑分层清晰,工具类型可复用
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

