← 中文资源

简体中文版本

Zod 判别联合表单路由

用 Zod 判别联合建模多形态登录表单:按 method 判别字段路由到各自 schema 与提交处理器,类型与分支一一对应。

阅读英文原文

示例说明

多形态表单用普通联合会得到字段全部可选的松散类型,判别联合以 method 字段为路由键:解析器先读判别值再套用对应分支 schema,错误信息也能精确指向具体分支。switch 到 Record 的处理器映射让每个 method 的处理函数与分支类型绑定,新增登录方式时缺失的处理器键会直接编译报错。

技术要点

  • 判别字段决定套用哪个分支 schema
  • handlers 映射键与联合成员一一对应
  • 每个分支错误独立定位不混淆

代码解读

三个分支各自约束字段:邮箱分支要求合法邮箱与八位密码;短信分支用正则锁定国际号码与六位验证码;OAuth 分支枚举提供商。submit 先 parse 得到精确分支的窄化值再查表派发,两段示例分别命中邮箱与短信分支。

使用建议

判别字段要在所有分支间互斥且必填。前端表单切换时清空另一分支的字段避免串值。新分支接入时先加 schema 再补处理器,让编译器驱动完成度。

代码示例

import { z } from 'zod';

const emailLogin = z.object({
  method: z.literal('email'),
  email: z.string().email(),
  password: z.string().min(8),
  remember: z.boolean().default(false),
});

const smsLogin = z.object({
  method: z.literal('sms'),
  phone: z.string().regex(/^\+\d{7,15}$/),
  code: z.string().regex(/^\d{6}$/),
});

const oauthLogin = z.object({
  method: z.literal('oauth'),
  provider: z.enum(['github', 'google']),
  idToken: z.string().min(20),
});

const LoginPayload = z.discriminatedUnion('method', [
  emailLogin, smsLogin, oauthLogin,
]);

type LoginPayload = z.infer<typeof LoginPayload>;

const handlers: Record<LoginPayload['method'], (p: never) => string> = {
  email: (p) => 'signing in with ' + (p as z.infer<typeof emailLogin>).email,
  sms: (p) => 'verifying sms for ' + (p as z.infer<typeof smsLogin>).phone,
  oauth: (p) => 'oauth via ' + (p as z.infer<typeof oauthLogin>).provider,
};

function submit(raw: unknown): string {
  const parsed = LoginPayload.parse(raw);
  return handlers[parsed.method](parsed as never);
}

console.log(submit({ method: 'email', email: '[email protected]', password: 'hunter2xx' }));
console.log(submit({ method: 'sms', phone: '+8613800138000', code: '123456' }));