TypeScript提前返回类型收窄:为何无法推断对象含指定属性?
我正在给对象编写类型守卫,用提前返回的方式保持代码可读性,示例是简化后的版本。我知道TypeScript的类型收窄存在不少局限,每个版本都会新增收窄逻辑,但有时候分不清是自己代码出错,还是TS暂不支持的特性。
我写了一个示例,原本以为能推断对象存在某个属性,结果却报错了,错误信息:
Property 'a' does not exist on type '{}'.
为什么在注释标注的位置,TS无法推断example存在a属性?
type Example = { a: number } const guard = (example: unknown): example is Example => { if ( example === undefined || example === null || typeof example === 'string' || typeof example === 'number' || Array.isArray(example) ) { return false } if ((example as Example).a === undefined) { return false } // 我原本期望这里能把example收窄为{a: unknown} // 因为上面的if语句已经排除了a为undefined的情况 // TS报错:Property 'a' does not exist on type '{}'. if (typeof example.a !== 'number') { return false } return true } const example: unknown = { a: 2 } const result = guard(example)
我还试过这两种写法,也没解决问题:
// 写法1 if (example.hasOwnProperty('a')) { return false }
// 写法2 if (Object.hasOwn(example, 'a')) { return false }
原因分析
类型断言不触发类型收窄:你用
(example as Example).a属于类型断言,TS只会在编译时临时把example当作Example类型处理,但不会修改对example的整体类型推断。所以执行完这个if语句后,TS仍然认为example是object类型(更准确说是{},因为已经排除了其他基础类型和数组),不会自动推断它包含a属性。hasOwnProperty/Object.hasOwn的逻辑错误:你写的判断逻辑是「如果存在a属性就返回false」,这和你想要的逻辑完全相反——你应该是想如果不存在a属性才返回false,所以条件需要取反。另外,早期TS版本对hasOwnProperty的类型收窄支持有限,新版本虽已支持,但前提是逻辑正确。
正确写法
要让TS正确收窄类型,需要使用TS能识别的原生类型守卫逻辑:
方法1:用in操作符(推荐)
in操作符是TS原生支持的类型守卫,能直接推断对象包含目标属性:
const guard = (example: unknown): example is Example => { if ( example === undefined || example === null || typeof example !== 'object' || // 简化判断,直接排除非object类型 Array.isArray(example) ) { return false } // 用in操作符判断a属性是否存在,TS会自动收窄类型 if (!('a' in example)) { return false } // 此时example已被推断为{ a: unknown } if (typeof example.a !== 'number') { return false } return true }
方法2:修正Object.hasOwn的逻辑
如果想用Object.hasOwn,需要把条件取反(确保TS版本在4.9及以上):
const guard = (example: unknown): example is Example => { if ( example === undefined || example === null || typeof example !== 'object' || Array.isArray(example) ) { return false } if (!Object.hasOwn(example, 'a')) { return false } if (typeof example.a !== 'number') { return false } return true }
补充说明
TS的类型收窄依赖于它能识别的特定语法和函数,比如in操作符、typeof、instanceof,以及TS内置的类型守卫函数。自定义断言或逻辑反向的判断,TS可能无法正确识别,导致类型推断不符合预期。
内容的提问来源于stack exchange,提问作者myol

