示例说明
多形态表单用普通联合会得到字段全部可选的松散类型,判别联合以 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' }));