可辨识联合重构表单校验的核心是用固定标签属性(如ruletype)区分规则类型,实现类型安全、自动收窄与易扩展;定义联合类型validationrule,配合switch进行类型守卫校验,新增规则时ts可静态检查遗漏分支。

用可辨识联合(Discriminated Unions)重构表单校验,核心是把“判断字段类型 + 执行对应规则”从一堆 if/else if 或嵌套 switch 里解放出来,让类型系统替你做分支选择和属性访问保障。它不靠运行时 typeof 或 instanceof,而是靠一个**固定字面量值的标签属性**(如 type、kind、validator)来区分不同校验逻辑,从而实现类型安全、自动收窄、易扩展。
定义带标签的校验规则类型
每个校验规则用接口描述,并共享一个字面量类型的可辨识属性(比如 ruleType),其余字段按需定制:
-
字符串长度校验:
{ ruleType: 'length'; min?: number; max?: number } -
邮箱格式校验:
{ ruleType: 'email'; required?: boolean } -
自定义正则校验:
{ ruleType: 'regex'; pattern: RegExp; message: string } -
必填校验:
{ ruleType: 'required'; trim?: boolean }
再用类型别名合并为联合类型:
type ValidationRule =
| { ruleType: 'length'; min?: number; max?: number }
| { ruleType: 'email'; required?: boolean }
| { ruleType: 'regex'; pattern: RegExp; message: string }
| { ruleType: 'required'; trim?: boolean };
编写类型守卫驱动的校验函数
不再写 if (rule.ruleType === 'length') {...} 这类易错、难维护的手动判断。改用 switch + 可辨识属性,TypeScript 会自动收窄每个 case 下的 rule 类型:
function validateField(value: unknown, rule: ValidationRule): { valid: boolean; message?: string } {
switch (rule.ruleType) {
case 'required':
const v = rule.trim && typeof value === 'string' ? value.trim() : value;
return { valid: v != null && v !== '', message: '此项为必填' };
case 'length':
if (typeof value !== 'string') return { valid: false, message: '仅支持字符串长度校验' };
const len = value.length;
if (rule.min !== undefined && len rule.max) {
return { valid: false, message: `最多 ${rule.max} 个字符` };
}
return { valid: true };
case 'email':
if (!rule.required && (value == null || value === '')) return { valid: true };
if (typeof value !== 'string') return { valid: false, message: '邮箱应为字符串' };
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return {
valid: emailRegex.test(value),
message: '请输入有效的邮箱地址'
};
case 'regex':
if (typeof value !== 'string') return { valid: false, message: rule.message };
return {
valid: rule.pattern.test(value),
message: rule.valid ? undefined : rule.message
};
}
}
注意:每个 case 中,rule 的类型已被精确收窄(例如进入 'length' 分支后,rule 类型就是 { ruleType: 'length'; min?: number; max?: number }),编辑器能智能提示可用属性,且访问 rule.pattern 在该分支下会直接报错——这就是类型安全的关键。
组合多条规则并保持类型完整性
表单字段往往有多个校验规则。定义一个字段配置类型,把可辨识联合作为数组元素:
type FormField = {
name: string;
value: unknown;
rules: ValidationRule[];
};
校验函数可递归遍历 rules,每一步都享受类型收窄。更进一步,你可以用泛型封装一个通用校验器,让调用侧无需关心分支细节:
function runValidations(field: FormField): { valid: boolean; messages: string[] } {
const messages: string[] = [];
for (const rule of field.rules) {
const result = validateField(field.value, rule);
if (!result.valid) messages.push(result.message!);
}
return { valid: messages.length === 0, messages };
}
添加新规则?只需在 ValidationRule 联合类型中加一项接口,再在 validateField 的 switch 中补一个 case —— 如果漏了,启用 --strictNullChecks 并给函数标注返回类型(如 : { valid: boolean; message?: string }),TS 就会立刻报错提醒你“未覆盖所有联合成员”,避免静默失败。
配套技巧提升实用性
-
用工厂函数生成规则:避免手写对象字面量出错,例如
required({ trim: true })返回{ ruleType: 'required', trim: true } - 结合 Zod 或 Yup 做运行时 Schema:可辨识联合管编译期类型,Zod 管运行时验证与错误聚合,二者互补
-
导出规则类型供表单组件消费:React 组件 props 可直接使用
rules: ValidationRule[],IDE 自动补全 + 类型检查全覆盖











