现在我们越来越多的在 UI 组件库里找到 Zod 的存在,Zod 是什么?我们为什么要用 Zod 呢?
根据其官网说法,zod 利用静态类型推断进行 TypeScript 优先模式验证。在 shadcn、nuxt ui 等组件库中,其主要是用来进行表单的校验;在一些 js 全栈项目里,它用来规定项目的数据结构。
我们就来简单的看一下它是怎么使用的。
这里我使用 pnpm 作为包管理工具,并使用 vue 项目举例。
安装
pnpm add zod
一些常用的内容
一些常用的字符串的验证
// 验证 z.string().max(5); // 最大长度 z.string().min(5); // 最小长度 z.string().length(5); // 字符长度 z.string().email(); z.string().url(); z.string().emoji(); z.string().uuid(); z.string().regex(regex); // 使用正则校验 z.string().includes(string); z.string().startsWith(string); z.string().endsWith(string); z.string().datetime(); // ISO 8601;默认值为无 UTC 偏移,选项见下文 z.string().ip(); // 默认为 IPv4 和 IPv6,选项见下文 // 转变 z.string().trim(); // 减除空白 z.string().toLowerCase(); // 小写化 z.string().toUpperCase(); // 大写化
创建字符串模式时,你可以自定义一些常见的错误信息
const name = z.string({ required_error: "Name is required", invalid_type_error: "Name must be a string", });
自定义错误消息
使用验证方法时,你可以传递一个附加参数,以提供自定义错误信息
z.string().min(5, { message: "Must be 5 or more characters long" }); z.string().max(5, { message: "Must be 5 or fewer characters long" }); z.string().length(5, { message: "Must be exactly 5 characters long" }); z.string().email({ message: "Invalid email address" }); z.string().url({ message: "Invalid url" }); z.string().emoji({ message: "Contains non-emoji characters" }); z.string().uuid({ message: "Invalid UUID" }); z.string().includes("tuna", { message: "Must include tuna" }); z.string().startsWith("https://", { message: "Must provide secure URL" }); z.string().endsWith(".com", { message: "Only .com domains allowed" }); z.string().datetime({ message: "Invalid datetime string! Must be UTC." }); z.string().ip({ message: "Invalid IP address" });
特定的数字验证
z.number().gt(5); // 最小为 5 z.number().gte(5); // alias .min(5) z.number().lt(5); // 最大为 5 z.number().lte(5); // alias .max(5) z.number().int(); // 必须是一个整数 z.number().positive(); // > 0 z.number().nonnegative(); // >= 0 z.number().negative(); // < 0 z.number().nonpositive(); // <= 0 z.number().multipleOf(5); // Evenly divisible by 5. Alias .step(5) z.number().finite(); // value must be finite, not Infinity or -Infinity z.number().safe(); // value must be between Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGE
和字符串一样,也可以传递第二个参数来自定义错误消息
对象类型
const userSchema = z.object({ username: z.string(), password: z.string() .min(5, { message: '密码最小为 5 位' }) .max(10, { message: '密码最大为 10 位' }), }) // 可以使用 z.infer来提取 schema 的 ts 类型 type User = z.infer<typeof userSchema> // 相当于 type User = {username: string, password: string}
受 TypeScript 内置的Pick和Omit工具类型的启发,所有 Zod 对象模式都有.pick和 .omit方法,可以返回一个修改后的版本
要想只保留某些 Key,使用 .pick .
const passwordSchema = userSchema.pick({ password: true }); type Password = z.infer<typeof passwordSchema>; // => type Password = {password: string}
要删除某些 Key,请使用 .omit .
const passwordSchema = userSchema.pick({ username: true }); type Password = z.infer<typeof passwordSchema>; // => type Password = {password: string}
optional
你可以用z.optional()使任何模式成为可选:
const userSchema = z.object({ username: z.string(), password: z.string(), age: z.number().optional() })
可以使用 coerce 对原始类型进行强制转换
// 对传入的 id 做校验并强制转换为数字类型 // /detail/:id const paramsSchema = z.object({ id: z.coerce.number() }) const { id } = paramsSchema.parse(router)
还有很多其他的校验内容,可以去 zod 的官网或 github 仓库去查看
Zod 官网地址zod.dev
使用技巧
1. 在 vue 项目中校验 router 传递的参数
这里使用safeParse
进行校验,校验失败后不会抛出错误,我们可以根据它返回的 success 是否为 true 来判断校验是否通过。并可以在 error 中得到校验失败的信息进行相应的处理。
<script lang="ts" setup> import { useRoute } from "vue-router" import { z } from "zod" const route = useRoute() const paramsSchema = z.object({ id: z.coerce.number({ message: "id必须是一个数字" }), }) const { success, data: params, error } = paramsSchema.safeParse(route.params) </script> <template> <div>detail {{ params?.id }}</div> <pre v-if="!success">{{ error }}</pre> </template>
同理,我们也可以校验它的 query 类型,此处便不再重复。
2. 传递数据时只传递指定的字段并对其进行校验
向接口传递数据时,我们有时会有一些多余的参数,此时我们可以构建一个 schema 来对传递的参数做校验,校验后获得的值为 schema 中规定的数据结构,可以帮助我们校验数据类型并且去除 schema 中未定义的多余参数
import { z } from "zod" const userSchema = z.object({ username: z.string(), age: z.number(), }) function submit() { // 这里模拟从别处拿到的值 const user = { username: "月空人", password: '123456', age: 26, id: 1, } console.log("submit", userSchema.parse(user)) // 这里打印的值是 submit {username: '月空人', age: 26} } submit()
同理,如果我们是一个 node 的接口,我们也可以使用这个技巧来向前端传递指定 schema 类型的数据。