如何实现TypeScript通用类型守卫函数,合并多守卫推断类型
实现TypeScript通用类型守卫函数:多守卫联合推断交叉类型
需求背景
需要实现一个通用类型守卫函数,接收一个对象和一组类型守卫函数(这些守卫入参类型一致,但断言的目标类型不同),当所有守卫校验通过时,TypeScript能自动推断该对象为所有守卫断言类型的交叉类型。
现有代码中,单个类型守卫能正常推断类型,但使用Array.every批量校验时无法自动推断交叉类型:
type HasFirstName = { firstName: string; }; type HasLastName = { lastName: string; }; const validateHasFirstName = (object: object): object is HasFirstName => 'firstName' in object; const validateHasLastName = (object: object): object is HasLastName => 'lastName' in object; const test = {}; // 单个守卫:类型推断正常 if (validateHasFirstName(test)) { test.firstName; // ✅ 可访问 } // 批量校验:无法推断交叉类型 const validators = [validateHasFirstName, validateHasLastName]; if (validators.every(v => v(test))) { test.firstName; // ❌ 无法访问,类型未推断 } // 期望实现的函数形态 function universalValidator(object: object, ...validators) { return validators.every(v => v(object)); } if (universalValidator(test, validateHasFirstName, validateHasLastName)) { test.firstName; // 期望此处可正常访问 test.lastName; }
解决方案
核心思路是提取每个类型守卫的断言类型,再将这些类型合并为交叉类型,而非直接交叉守卫函数本身。
1. 定义工具类型
// 定义类型守卫函数的通用结构:入参为T,断言为T的子类型U type TypeGuard<T, U extends T> = (obj: T) => obj is U; // 从类型守卫函数中提取出它断言的目标类型U type ExtractGuardType<G> = G extends TypeGuard<any, infer U> ? U : never; // 将联合类型转换为交叉类型(复用经典实现) type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
2. 实现通用类型守卫函数
function universalValidator<T, G extends TypeGuard<T, any>>( obj: T, ...validators: G[] ): obj is T & UnionToIntersection<ExtractGuardType<G>> { // 执行所有守卫校验 return validators.every(validator => validator(obj)); }
3. 测试验证
const test = {}; if (universalValidator(test, validateHasFirstName, validateHasLastName)) { test.firstName; // ✅ 类型推断为string,可正常访问 test.lastName; // ✅ 类型推断为string,可正常访问 }
关键说明
- 之前尝试交叉守卫函数本身的方案错误,因为我们需要的是合并断言的类型,而非合并函数类型
ExtractGuardType通过条件类型的infer关键字,从守卫函数中提取出它断言的目标类型UnionToIntersection将多个守卫的断言类型(联合类型)转换为交叉类型,确保对象同时满足所有守卫的类型约束- 函数泛型
T绑定输入对象的原始类型,最终返回的类型断言是T与所有守卫断言类型的交叉,保证类型推断的准确性
内容的提问来源于stack exchange,提问作者Tomáš Hübelbauer
相关产品推荐
相关产品推荐

