TypeScript中如何定义键同名但值类型不同的Model与SerialisedModel接口?
解决TypeScript中接口字段同步映射的类型安全问题
我完全懂你的痛点——手动维护两个接口的字段映射太容易踩坑,字段重命名后编译器没反应,等运行时出问题才发现简直是噩梦。好在TypeScript的映射类型能完美搞定这个需求,让编译器帮你把住字段一致性的关卡。
核心思路:用映射类型强制键完全匹配
我们可以借助TypeScript的映射类型,让Model自动继承SerialisedModel的所有键,同时允许你为每个键指定不同的值类型。这样一来,只要SerialisedModel的字段有任何修改(重命名、新增、删除),编译器都会立刻在Model或转换函数里抛出错误,从根源避免运行时问题。
具体实现步骤
1. 定义序列化后的接口
先明确你的SerialisedModel结构:
interface SerialisedModel { name: string; age: string; // 序列化后存储为字符串类型 email: string; }
2. 用映射类型定义目标Model
通过[K in keyof SerialisedModel]遍历SerialisedModel的所有键,确保Model拥有完全相同的键,同时自定义每个键的类型:
// 映射类型:Model的键与SerialisedModel完全一致,值类型按需转换 type Model = { [K in keyof SerialisedModel]: K extends 'age' ? number // age字段转成number类型 : string; // 其他字段保持string类型 };
这样定义后,Model的结构会自动同步SerialisedModel的键,等价于手动写:
// interface Model { // name: string; // age: number; // email: string; // }
3. 实现类型安全的转换函数
现在写转换函数时,TypeScript会严格检查你是否处理了所有字段,并且类型是否匹配:
function deserialize(serialised: SerialisedModel): Model { return { name: serialised.name, age: parseInt(serialised.age, 10), // 必须返回number类型,否则编译器报错 email: serialised.email }; }
4. 测试字段重命名的情况
假设你把SerialisedModel中的age改成yearsOld:
interface SerialisedModel { name: string; yearsOld: string; // 字段重命名 email: string; }
此时编译器会立刻抛出两个错误:
Model的映射类型会自动要求yearsOld字段,原来的age字段会被标记为不存在deserialize函数中的age: parseInt(serialised.age, 10)会报错,因为serialised已经没有age属性了
完美!编译器帮你提前发现了问题,不用等到运行时才踩坑。
进阶:通用类型转换工具
如果需要处理多个类似的映射场景,可以封装一个通用的类型工具:
// 通用类型:将SerialisedType的字段按TypeMap规则转换类型 type MapSerializedType<SerialisedType, TypeMap> = { [K in keyof SerialisedType]: SerialisedType[K] extends keyof TypeMap ? TypeMap[SerialisedType[K]] : SerialisedType[K]; }; // 示例:定义string转number的映射规则 type StringToNumberMap = { string: number; }; // 用通用工具生成Model type Model = MapSerializedType<SerialisedModel, StringToNumberMap>;
这样你可以更灵活地批量处理类型转换,同时依然保持键的一致性。
内容的提问来源于stack exchange,提问作者Ray Booysen
相关产品推荐
相关产品推荐

