如何为TypeScript组合式校验器配置类型实现编译时校验
TypeScript 类型守卫组合函数的类型约束实现
我定义了类型守卫函数的签名:
type Validator<T> = (doc: any) => doc is T;
现在需要实现union和intersection两个组合函数,要求配置参数validators的类型,让TypeScript编译器能自动校验:
- 对于
union<T>:传入的每个校验器对应的类型必须是T的子类型,组合后能正确守卫T - 对于
intersection<T>:传入的所有校验器对应的类型的交叉必须等于T,不能少传必要的校验器
期望的校验效果如下:
interface FooOne { a: string; } interface FooTwo { b: string; } interface FooThree { c: string } type Bar = FooOne | FooTwo type Baz = FooOne & FooTwo // 基础校验器生成函数示例 const validator = <T>(): Validator<T> => (doc: any): doc is T => true; const oneValidator = validator<FooOne>() const twoValidator = validator<FooTwo>() const threeValidator = validator<FooThree>() const barValidator = union<Bar>([oneValidator, twoValidator]) // 正常通过 const barValidator = union<Bar>([oneValidator]) // 正常通过,FooOne是Bar的子类型 const barValidator = union<Bar>([oneValidator, twoValidator, threeValidator]) // 报错,FooThree不属于Bar const bazValidator = intersection<Baz>([oneValidator, twoValidator]) // 正常通过 const bazValidator = intersection<Baz>([oneValidator]) // 报错,仅FooOne无法覆盖Baz的全部属性 const bazValidator = intersection<Baz>([oneValidator, twoValidator, threeValidator]) // 报错,FooThree不属于Baz的组成部分
1. 实现union组合函数的类型约束
核心逻辑是确保每个传入的校验器对应的类型U是T的子类型,这样所有校验器的联合就能覆盖T的范围:
function union<T, U extends T>(validators: Validator<U>[]): Validator<T> { return (doc: any): doc is T => { return validators.some(validator => validator(doc)); }; }
逻辑说明
- 泛型
U extends T约束了数组中所有校验器的目标类型必须是T的子类型 - 只要有一个校验器通过,就认为当前值属于
T类型,符合联合类型的判断逻辑
2. 实现intersection组合函数的类型约束
核心是确保传入的所有校验器类型的交叉结果等于T,同时每个类型都是T的子类型:
// 辅助类型:将数组中所有校验器的类型交叉合并 type CombineValidators<T extends Validator<any>[]> = T extends [Validator<infer U>, ...infer Rest] ? Rest extends Validator<any>[] ? U & CombineValidators<Rest> : U : unknown; function intersection<T>( validators: CombineValidators<typeof validators> extends T ? Validator<any>[] : never ): Validator<T> { return (doc: any): doc is T => { return validators.every(validator => validator(doc)); }; }
逻辑说明
CombineValidators类型会递归提取数组中每个校验器的目标类型,并将它们交叉合并- 通过条件类型判断合并后的交叉类型是否等于
T,不满足则参数类型变为never,触发编译错误 - 只有所有校验器都通过,才认为当前值属于
T类型,符合交叉类型的判断逻辑
验证效果
使用上述实现后:
- 传入不符合
T范围的校验器(如给union<Bar>传threeValidator)会直接报错 - 给
intersection<Baz>只传oneValidator时,合并后的交叉类型是FooOne,不等于Baz,触发报错 - 符合预期的调用会正常通过编译
内容的提问来源于stack exchange,提问作者theahura
相关产品推荐
相关产品推荐

