开启strictNullChecks时,TypeScript两种嵌套属性检查写法为何表现不同?
我在启用strictNullChecks设置时遇到了类型错误,先看一下定义的类型、枚举和函数:
type MyType = { prop: MyEnum; // ... 其他属性 }; enum MyEnum { Value1, Value2, // ... 其他枚举值 } async function handleAction(item: MyType) { // 函数逻辑 }
另外还有一个MyRecord类型的对象myRecord,它的nestedItem属性可能为null:
type MyRecord = { nestedItem: MyType | null; // ... 其他属性 }; const myRecord: MyRecord = { nestedItem: null, // 也可以是MyType类型的对象 // ... 其他属性 };
我需要检查nestedItem是否存在且其prop属性等于MyEnum.Value1,尝试了两种写法,结果却不一样:
方式1:使用中间变量存储条件结果
const isCertainCondition = myRecord.nestedItem && myRecord.nestedItem.prop === MyEnum.Value1; if (isCertainCondition) { await handleAction(myRecord.nestedItem); // 此处触发类型错误 }
触发的TypeScript错误:
Argument of type 'MyType | null' is not assignable to parameter of type 'MyType'.
Type 'null' is not assignable to type 'MyType'
方式2:直接在if条件中判断
if ( myRecord.nestedItem && myRecord.nestedItem.prop === MyEnum.Value1 ) { await handleAction(myRecord.nestedItem); // 无类型错误 }
这两种写法为什么在TypeScript中表现不同?
原因解析
核心差异在于TypeScript的类型收缩(Type Narrowing)机制的作用范围:
方式2的逻辑:当你在
if条件语句中直接检查myRecord.nestedItem时,TypeScript会进行实时的类型分析。一旦条件判断为真,它能明确知道myRecord.nestedItem此时不可能是null,且prop等于MyEnum.Value1,因此会自动将myRecord.nestedItem的类型从MyType | null收缩为MyType,所以传入handleAction时不会报错。方式1的逻辑:你把条件判断的结果存储到了
isCertainCondition变量中,但TypeScript的类型系统无法追踪这个布尔变量和myRecord.nestedItem之间的逻辑关联。它只知道isCertainCondition是一个布尔值,无法通过这个变量反向推导myRecord.nestedItem的类型已经发生变化,因此myRecord.nestedItem仍然保持MyType | null的联合类型,传入要求MyType的handleAction就会触发类型不兼容的错误。
可选解决方案(保留中间变量写法)
如果想保留中间变量的写法,可以通过自定义类型守卫函数让TypeScript识别类型关联:
// 定义类型守卫函数,明确返回值的类型断言 function isMyTypeWithValue1(item: MyType | null): item is MyType { return item !== null && item.prop === MyEnum.Value1; } // 使用类型守卫 const isCertainCondition = isMyTypeWithValue1(myRecord.nestedItem); if (isCertainCondition) { await handleAction(myRecord.nestedItem); // 无类型错误 }
类型守卫函数通过item is MyType的断言,告诉TypeScript当函数返回true时,传入的item一定是MyType类型,这样就能实现跨变量的类型收缩。
内容的提问来源于stack exchange,提问作者Puukallistaja

