TypeScript 意外行为求解:为何未检测到对象合并类型问题?
TypeScript未检测到对象展开覆盖问题的原因解析
问题现象
将两个对象通过Object.values转为数组后展开合并为新对象时,object1的前四个值会被object2的对应索引值覆盖,合并后的mergedObjects中实际不存在这些被覆盖的值,但TypeScript却允许将这些被覆盖的值传入tryThis函数;而传入完全不属于两个对象的取值时,TS会正常报错。
示例代码
const object1 = { one: 'hey brother', two: 'hi bro', three: 'how are you?', four: 'good thanks', five: 'Great', six: 'bye' } as const const object2 = { one: 'wrong', two: 'falsch', three: 'right', four: 'recht' } as const const mergedObjects = { ...Object.values(object1), ...Object.values(object2) } as const type MergedObjectValue = (typeof mergedObjects)[number] const tryThis = (value: MergedObjectValue) => { // do nada } console.log("mergedObjects", mergedObjects) // TS 不报错,但值实际不在 mergedObjects 中 tryThis('hey brother') // TS 不报错,值存在于 mergedObjects 中 tryThis('wrong') // TS 报错,值完全不存在 tryThis('this is not in either')
问题根源
TypeScript的类型系统是静态分析机制,不会模拟运行时的执行逻辑,具体原因可拆解为以下几点:
Object.values的类型推导:因为使用了as const断言,Object.values(object1)会被推导为包含所有值的只读数组类型readonly ["hey brother", "hi bro", ..., "bye"],Object.values(object2)同理,类型是固定的集合。数组展开到对象的类型合并规则:当把数组展开到对象中时,TS会直接合并两个数组的所有值的联合类型,作为对象值的类型。它不会考虑“后面数组的同索引键会覆盖前面数组”这个运行时行为,只会认为合并后的对象的取值是两个数组所有值的集合。
索引签名的类型生成:最终
mergedObjects的类型被推导为带有数字索引的对象,索引对应的类型是两个数组值的完整联合类型。所以MergedObjectValue就是这个联合类型,自然允许传入被覆盖的'hey brother'这类值,而完全不在联合里的字符串会被TS拦截。
简单来说:TS只关注你合并了哪些值的类型,不会追踪这些值在运行时会不会被覆盖。
内容的提问来源于stack exchange,提问作者Alfie Freeman
相关产品推荐
相关产品推荐

