如何编写保留前置类型信息的TypeScript复杂组合类型类型守卫?
为TypeScript复杂组合类型编写类型守卫的可行性
是否可以为包含多个与(&)、或(|)语句的TypeScript复杂组合类型编写类型守卫,以检测其中某种子类型的存在?
示例类型定义
interface Type1 { cat: string } interface Type2 { dog: boolean } interface Type3 { mouse: number } interface Type4 { elephant: string | number } type CombinatoryType = (Type1 & (Type2 | Type3)) | Type4;
预期的类型守卫行为
若存在检测Type2和Type4的类型守卫(分别命名为hasType2和hasType4),按以下顺序调用应得到对应类型推导结果:
function doSomething(data: CombinatoryType) { if (hasType2(data)) { // 'data' 现在的类型为: (Type1 & Type2) | Type4 if (hasType4(data)) { // 'data' 现在的类型为: Type1 & Type2 & Type4 } } }
按相反顺序调用则应得到以下结果:
function doSomething(data: CombinatoryType) { if (hasType4(data)) { // 'data' 现在的类型为: (Type1 & (Type2 | Type3)) & Type4 if (hasType2(data)) { // 'data' 现在的类型为: Type1 & Type2 & Type4 } } }
注意要点
- 类型守卫不应丢失前置类型断言产生的类型信息
- 类型守卫不能使用显式返回类型(如
data is (Type1 & Type2) | Type4),因为组合类型可能非常冗长,且会干扰VSCode的类型推导
解决方案
可以通过泛型类型守卫实现需求,利用TypeScript的条件类型自动推导交集/并集的收缩结果,无需硬写冗长的目标类型。
实现通用类型守卫工具
先编写一个通用的类型守卫生成函数,用于检测对象是否包含目标类型的特征属性:
type KeysOfType<T> = keyof T; function hasType<T extends object>( obj: unknown, key: KeysOfType<T> ): obj is T & typeof obj { return typeof obj === 'object' && obj !== null && key in obj; }
针对示例的具体类型守卫
基于上述通用工具,生成hasType2和hasType4:
const hasType2 = (obj: unknown): obj is Type2 & typeof obj => hasType<Type2>(obj, 'dog'); const hasType4 = (obj: unknown): obj is Type4 & typeof obj => hasType<Type4>(obj, 'elephant');
验证推导效果
此时调用这两个类型守卫,TypeScript会自动保留前置的类型信息,实现预期的类型收缩:
function doSomething(data: CombinatoryType) { if (hasType2(data)) { // 推导类型:(Type1 & Type2) | Type4 data.cat; // 正常访问Type1的属性 data.dog; // 正常访问Type2的属性 if (hasType4(data)) { // 推导类型:Type1 & Type2 & Type4 data.elephant; // 正常访问Type4的属性 } } if (hasType4(data)) { // 推导类型:(Type1 & (Type2 | Type3)) & Type4 data.elephant; // 正常访问Type4的属性 data.cat; // 正常访问Type1的属性 if (hasType2(data)) { // 推导类型:Type1 & Type2 & Type4 data.dog; // 正常访问Type2的属性 } } }
原理说明
- 泛型
T指定要检测的目标类型,key参数约束为T的键,确保检测的属性是目标类型的特征标识 - 返回类型
obj is T & typeof obj利用了TypeScript的类型交集特性:将当前obj的已有类型(可能是组合类型的一部分)与目标类型T取交集,自动保留前置的类型信息 - 这种方式无需硬写复杂的组合类型,完全由TypeScript自动推导,避免了冗长代码和类型维护问题
内容的提问来源于stack exchange,提问作者sookie
相关产品推荐
相关产品推荐

