You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现类型安全的createValidation函数:组合验证谓词返回Ok/Error联合类型

实现类型安全的链式验证函数createValidation

完全可以实现这个功能,通过TypeScript的可变元组类型和类型谓词约束,既能保证验证逻辑的链式类型推导,又能在类型不匹配时触发编译错误。以下是完整的实现方案:

完整代码实现

// 定义单个验证规则:[谓词函数, 错误提示文本]
type ValidationRule<TIn, TOut extends TIn = TIn> = [
  (value: TIn) => value is TOut,
  string
] | [
  (value: TIn) => boolean,
  string
];

// 递归约束验证规则链:后续规则的输入类型必须兼容前一个规则的输出类型
type ValidationRulesChain<TStart> =
  | [ValidationRule<TStart>]
  | [ValidationRule<TStart, infer TNext>, ...ValidationRulesChain<TNext>];

// 定义最终返回的验证函数类型
type Validator<TIn, TOut> = (value: TIn) => 
  | { ok: false; error: string }
  | { ok: true; data: TOut };

function createValidation<TStart, TRules extends ValidationRulesChain<TStart>>(
  ...rules: TRules
): Validator<TStart, TRules extends [...infer _, ValidationRule<infer TLast>] ? TLast : TStart> {
  return (value: TStart) => {
    for (const [predicate, errorMsg] of rules) {
      if (!predicate(value)) {
        return { ok: false, error: errorMsg };
      }
    }
    // 所有验证通过后,value的类型已被最后一个谓词约束为TLast
    return { ok: true, data: value as any };
  };
}

关键逻辑说明

  • ValidationRule<TIn, TOut>:兼容两种谓词类型——带类型断言的守卫函数(value is TOut)和普通布尔返回的验证函数,TOut默认等于TIn,适配非类型守卫的场景。
  • ValidationRulesChain<TStart>:通过递归元组约束,强制后续规则的输入类型必须是前一个规则的输出类型,形成链式的类型依赖。
  • 返回类型推导:利用条件类型提取规则链最后一个元素的输出类型TLast,作为验证成功时data的类型,保证类型安全。

正确使用示例

const validateString = createValidation(
  [(value: unknown): value is string => typeof value === 'string', 'Value is not a string'],
  [(value: string) => value.length > 0, 'String can not be empty'],
  [(value: string) => value.length < 10, 'String is too long'],
);

// 调用验证函数
const validResult = validateString("hello");
if (validResult.ok) {
  console.log(validResult.data.length); // 类型安全,data已被推导为string
}

const emptyResult = validateString("");
// 返回 { ok: false, error: "String can not be empty" }

类型错误触发场景

当规则链中出现类型不兼容的谓词时,TypeScript会直接抛出编译错误:

const validateString = createValidation(
  [(value: unknown): value is string => typeof value === 'string', 'Value is not a string'],
  [(value: string) => value.length > 0, 'String can not be empty'],
  [(value: string) => value.length < 10, 'String is too long'],
  [(value: number) => value === 123, 'A number?'], // ❌ TypeScript错误:类型不兼容
);

这里第四个规则的输入类型是number,但前一个规则的输出类型是string,类型不匹配,触发编译错误,完全符合需求。

内容的提问来源于stack exchange,提问作者whysoserious

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 09:57:25