为何TypeScript无法通过断言收窄联合类型推断必填属性?
场景说明:
假设有一个函数接收单一参数,该参数为两种重叠类型组成的联合类型对象,其中一个联合分支包含额外必填属性:
// 假设类型A、B、C为任意定义的类型 function myFunc(inputParams: {commonProp: A | B} | {commonProp: C, extraProp: string}) { // 后续逻辑 }
在函数体内,我们断言commonProp为单一类型,理论上应能推断extraProp是否存在。例如,若inputParams.commonProp为B类型,则inputParams.extraProp应为undefined;若为C类型,则extraProp应为必填属性:
function isC(o: any): o is C { // 判断并返回o是否为C类型 } function myFunc(inputParams: {commonProp: A | B} | {commonProp: C, extraProp: string}) { if (isC(inputParams.commonProp)) { const myVar = inputParams.extraProp; // 预期可行但实际报错 } }
实际运行时报错:
Property 'extraProp' does not exist on type '{ commonProp: A | B; } | { commonProp: C; extraProp: string; }'.
Property 'extraProp' does not exist on type '{ commonProp: A | B; }'
完整代码示例:
type A = { t: "a" } type B = { t: "b" } type C = { t: "c" } function isC(o: A | B | C): o is C { return o.t === "c"; } function myFunc(inputParams: {commonProp: A | B} | {commonProp: C, extraProp: string}) { if (isC(inputParams.commonProp)) { const myVar = inputParams.extraProp; } }
原因分析
TypeScript的类型收窄逻辑无法通过子属性的类型断言反向推导父对象的联合类型分支。
你的联合类型定义包含两个对象分支:
{commonProp: A | B}{commonProp: C, extraProp: string}
当你用isC(inputParams.commonProp)断言子属性commonProp是C类型时,TypeScript只能确认inputParams.commonProp的类型为C,但无法关联到父对象必须是第二个分支——因为从类型定义上看,第一个分支的commonProp并没有被约束为不能是C(虽然你实际不会这么传,但TypeScript只基于类型定义做判断)。
简言之:TypeScript不会自动建立「子属性类型→父对象分支」的关联,它认为即使commonProp是C,父对象仍可能属于{commonProp: A | B}分支。
解决方案
1. 用类型守卫直接断言父对象类型
修改类型守卫函数,直接判断并断言整个inputParams的类型:
function hasExtraProp(input: {commonProp: A | B} | {commonProp: C, extraProp: string}): input is {commonProp: C, extraProp: string} { return (input as {commonProp: C}).commonProp.t === "c"; } function myFunc(inputParams: {commonProp: A | B} | {commonProp: C, extraProp: string}) { if (hasExtraProp(inputParams)) { const myVar = inputParams.extraProp; // 正常通过类型检查 } }
2. 给联合类型分支添加互斥鉴别属性
如果允许修改类型定义,可以给两个分支添加鉴别属性,让TypeScript自动完成类型收窄:
type Type1 = { type: "type1", commonProp: A | B } type Type2 = { type: "type2", commonProp: C, extraProp: string } function myFunc(inputParams: Type1 | Type2) { if (inputParams.type === "type2") { const myVar = inputParams.extraProp; // 自动收窄成功 } }
3. 使用类型断言绕过检查(不推荐)
如果只是临时解决,可以直接用类型断言,但会失去TypeScript的类型安全保障:
function myFunc(inputParams: {commonProp: A | B} | {commonProp: C, extraProp: string}) { if (isC(inputParams.commonProp)) { const myVar = (inputParams as {commonProp: C, extraProp: string}).extraProp; } }
内容的提问来源于stack exchange,提问作者zr0gravity7

