TypeScript中使用keyof类型索引对象赋值的TS2322报错问题
问题背景
定义了包含可选属性的接口:
interface AnInterface { foo?: number; bar?: string; baz?: boolean; }
尝试编写根据指定键修改对象值的函数时,直接执行output[keyVal.property] = keyVal.val会触发TS2322类型错误(如Type 'number' is not assignable to type 'undefined'),即使先判断val的类型也无法解决。但将属性值赋值给临时变量后再修改则不会报错,且不想通过添加索引签名(会放宽键的限制)来解决问题。
错误原因
TypeScript无法在keyVal为联合类型时,自动建立keyVal.property和keyVal.val的精确类型关联。比如在typeof keyVal.val === 'string'分支中,TS仅知道val是string,但无法确定property必然是对应类型的bar,因此会将output[keyVal.property]的类型推断为所有可选属性类型的模糊集合,导致赋值时类型不兼容。
解决方案
方案1:泛型函数(推荐)
通过泛型让TypeScript追踪keyVal的具体键类型,明确键与值的类型对应关系,实现完全类型安全:
function modifyObjectItem<K extends keyof AnInterface>( objectToModify: AnInterface, keyVal: { property: K; val: AnInterface[K] } ) { const output = { ...objectToModify }; output[keyVal.property] = keyVal.val; // 无类型错误 return output; } // 使用示例 const originalObj: AnInterface = { foo: 123 }; const updatedObj = modifyObjectItem(originalObj, { property: 'bar', val: 'hello' });
此方案无需额外类型判断,TypeScript会自动校验keyVal中键和值的类型匹配性。
方案2:自定义类型守卫
如果必须保留联合类型的keyVal参数,可通过自定义类型守卫在分支中窄化类型:
type AnInterfaceKeyVal = { [P in keyof AnInterface]: { property: P; val: AnInterface[P]; }; }[keyof AnInterface]; // 自定义类型守卫 function isBarKeyVal(keyVal: AnInterfaceKeyVal): keyVal is { property: 'bar'; val: string } { return keyVal.property === 'bar'; } function isFooKeyVal(keyVal: AnInterfaceKeyVal): keyVal is { property: 'foo'; val: number } { return keyVal.property === 'foo'; } function isBazKeyVal(keyVal: AnInterfaceKeyVal): keyVal is { property: 'baz'; val: boolean } { return keyVal.property === 'baz'; } function modifyObjectItem( objectToModify: AnInterface, keyVal: AnInterfaceKeyVal ) { const output = { ...objectToModify }; if (isBarKeyVal(keyVal)) { output[keyVal.property] = keyVal.val; } else if (isFooKeyVal(keyVal)) { output[keyVal.property] = keyVal.val; } else if (isBazKeyVal(keyVal)) { output[keyVal.property] = keyVal.val; } return output; }
通过类型守卫,TypeScript能在每个分支中确定keyVal的具体类型,从而允许安全赋值。
方案3:类型断言(简易场景)
在确保逻辑安全的前提下,直接使用类型断言跳过TypeScript的类型检查:
function modifyObjectItem( objectToModify: AnInterface, keyVal: AnInterfaceKeyVal ) { const output = { ...objectToModify }; switch (typeof keyVal.val) { case 'string': output[keyVal.property as 'bar'] = keyVal.val; break; case 'number': output[keyVal.property as 'foo'] = keyVal.val; break; case 'boolean': output[keyVal.property as 'baz'] = keyVal.val; break; default: break; } return output; }
注意:此方式需要开发者自行保证逻辑正确性,TypeScript不再做类型校验。
临时变量赋值不报错的原因
将output[keyVal.property]赋值给临时变量时,TypeScript会根据当前分支的val类型推断变量的具体类型(如string | undefined),而keyVal.val的类型(如string)是该变量类型的子集,因此赋值合法。但直接操作对象属性时,TS无法建立键与值的类型关联,导致类型不匹配错误。
内容的提问来源于stack exchange,提问作者user453445

