TypeScript严格模式下动态访问不同对象属性的类型标注方法
问题描述
现有多类结构不同的对象,所有对象都通过type字段标记自身类型,且不同类型对象使用不同的属性键存储同类型数组:例如下方代码中类型为a的对象将只读数字数组存在childrenA键下,类型为b的对象将只读数字数组存在childrenB键下。
额外定义了HIERARCHY常量映射type值和对应数组的存储键,需要实现getChildren函数,传入对象返回对应存储的数组,要求在TypeScript严格模式下无类型报错。
初始实现代码如下:
const a = { type: "a", childrenA: [ 1, 2, 3 ] } as const; const b = { type: "b", childrenB: [ 4, 5, 6 ] } as const; const HIERARCHY = { a: "childrenA", b: "childrenB", } as const; const getChildren = <T extends typeof a | typeof b>(obj: T) => { const ret = obj[HIERARCHY[obj.type]]; return ret; }; const AOrB = Math.random() > 0.5 ? a : b; getChildren(AOrB);
已定位到类型报错的核心原因:
- 当入参是
a | b联合类型时,obj.type的推导类型为"a" | "b" - 对应
HIERARCHY[obj.type]的推导类型为"childrenA" | "childrenB" - TypeScript不会跟踪两个联合类型值的对应匹配关系,会默认认为存在「用
childrenB键访问a类型对象」「用childrenA键访问b类型对象」的非法可能,因此抛出类型错误,无法通过严格模式校验。
实现方案
这个是TypeScript中典型的关联记录类型校验问题,TS本身不会自动跨表达式跟踪联合类型的对应关系,我们可以通过泛型+类型映射的方式明确类型对应规则,仅需要极小范围的安全类型断言即可通过严格模式检查,不需要使用any或者@ts-ignore绕过校验。
修正后的完整代码:
const a = { type: "a", childrenA: [ 1, 2, 3 ] } as const; const b = { type: "b", childrenB: [ 4, 5, 6 ] } as const; const HIERARCHY = { a: "childrenA", b: "childrenB", } as const; // 定义所有支持的对象类型联合 type SupportObj = typeof a | typeof b; // 从HIERARCHY常量推导type到存储键的类型映射 type TypeToKeyMap = typeof HIERARCHY; /** * 传入对象获取对应存储的子数组 */ const getChildren = <T extends SupportObj>(obj: T): T[TypeToKeyMap[T['type']]] => { // 此处断言为安全断言:HIERARCHY为只读常量,键值映射与对象type严格对应,不存在非法访问 return obj[HIERARCHY[obj.type]] as T[TypeToKeyMap[T['type']]]; }; // 测试:传入联合类型对象时,返回值会被正确推导为 readonly [1,2,3] | readonly [4,5,6] const AOrB = Math.random() > 0.5 ? a : b; const children = getChildren(AOrB);
关键逻辑说明:
- 用泛型
T约束入参类型,而非直接将参数标注为联合类型,TS可以基于传入的具体参数类型推导对应返回值,不会丢失类型关联 - 返回值类型
T[TypeToKeyMap[T['type']]]完全对齐运行时逻辑:先取入参的type值,映射到对应存储键,再取该键在入参对象上的属性类型 - 函数内部的类型断言是可控的:因为
HIERARCHY是只读常量,映射关系在定义后不会被修改,运行时不会出现键和对象类型不匹配的情况,断言仅用于告知TS我们已经保证了这层对应关系,没有破坏类型安全。
后续如果新增对象类型,只需要在定义新对象后,将其加入SupportObj联合类型、在HIERARCHY中补充对应type和存储键的映射即可,getChildren函数不需要任何修改,类型会自动适配。
内容的提问来源于stack exchange,提问作者Teiem
相关产品推荐
相关产品推荐

