简介
使用 Schema 进行表单字段验证是在前端应用程序中实现高级表单的一个重要方面。表单 Schema 有助于定义表单所处理数据的具体形态。
在一个基于 TypeScript
的 React
表单中,Schema
验证涉及对所处理的数据库实体进行正确的类型声明和注解。随着应用程序中数据实体或资源的增长,类型重叠、映射、相互转换、派生和其他操作变得不可避免。这使得在一个不断增长的代码库中从头开始对表单 Schema
进行静态类型定义变得非常繁琐。
Zod 是一个 TypeScript-first
的库,旨在解决这些问题。它提供了一套经过实战检验的全面 API,用于在基于 React
的表单中声明和应用类型完备的表单 Schema
验证。凭借其用于声明 primitives
、objects
的验证器方法,以及与 TypeScript
中类型操作相对应的 Schema
派生 API,Zod
使得表单 Schema
的静态类型定义变得极其灵活。Zod
还提供了一些额外的功能,通过其验证器的精度、refinement
和 transformation
方法来实现高度特定的验证规则。它是一个无依赖的库,与主流的基于 React
的表单解决方案(如 Formik 和 React Hook Form)能很好地配合。
在本文中,我们将演示如何在一个基于 TypeScript
的 React
应用中,使用 Zod
和 React Hook Form
的一些主要 API 来实现类型完备的表单 Schema
验证。本文重点介绍如何使用 React Hook Form
实现 Zod
Schema
验证。我们将通过一个在普通 React Hook Form
基础上引入 Zod
的示例来涵盖基础知识,并介绍一些重要的 Zod
API,以实现更精细的 Schema
验证。
概述
我们首先要理解什么是表单 Schema
,它在大型代码库中的重要性,以及为什么在基于 TypeScript
的 React
应用中需要对表单 Schema
验证进行适当的静态类型定义。我们将阐述 Zod
如何帮助在 React
应用中实现类型完备的表单 Schema
,并讨论用于 React Hook Form
的 Zod resolver
在将 Zod
Schema
集成到 React Hook Form
验证中所扮演的角色。
我们将在将一个现有的基于普通 React Hook Form
的应用中的表单迁移到 Zod
的过程中涵盖 Zod
的基础知识。我们将通过一个 Create Post
表单的例子,详细说明什么是 Zod
Schema
和验证器,以及如何使用 Zod
primitives
(如 string
)来声明验证器。我们将学习它们如何通过 object()
方法帮助构建复杂的数据结构,并演示 Zod
的验证器精度 API(如 min()
、max()
、email()
等)的示例。我们将探讨 parse()
方法在决定用于验证的运行时 Schema
中所扮演的核心角色。我们还将研究如何使用 infer()
API 从 Schema
生成 Zod
类型。
在本文的后半部分,我们将考虑一个 <EditProfile />
表单组件,以实现 Zod
的默认值与 default()
方法。我们将介绍 Zod
如何通过类似 TypeScript
的实用工具(如 partial()
、pick()
和 omit()
)来实现直观的 Schema
派生。我们使用它们来帮助轻松实现复杂的表单字段需求,而这些需求在其他情况下通常很耗时。
最后,我们将讨论 Zod
的 refinements
和 transformations
。我们将研究使用 refine()
方法构建自定义精确规则以及使用 transform()
方法转换字段值的示例。
为什么我们应该使用表单 Schema
表单 Schema
通常是有益的,因为它们专注于表单中处理的数据实体。Schema
使表单字段的声明和验证易于实现。Schema
还有助于编写 DRY
(Don’t Repeat Yourself) 的代码。通过在应用程序实体增加时保持声明的一致性,它们有助于代码的稳定性、可维护性和可伸缩性。
Zod 为何如此特别?
Zod
作为一个经过充分测试的 Schema
验证库,在与像 Formik
和 React Hook Form
这样功能丰富的解决方案一起使用时,对于实现功能丰富的表单体验非常有用。
其他 Schema
验证库如 Yup 和 Joi 也存在。然而,Zod
的独特之处在于其广泛的静态类型 API
接口,它模仿了 TypeScript
用于类型声明、派生、推断和与数据本身结合使用的其他各种操作的 API
。这使得 Zod
在不断增长的 TypeScript
代码星球中极为友好,在这些代码星球中,随着应用程序实体的增加,类型映射、派生和数据操作变得不可避免。
Zod 是如何工作的
以下是 Zod
的工作原理:
Zod
通过zod
对象(或任何其他标识符)提供一个Zod
instance
,该instance
公开了用于声明验证器的API
。Zod
验证器是单独的验证声明。最简单的验证器是原始类型,并带有相关的错误消息。Zod
中的验证器通常代表表单字段或数据库实体的属性。Zod
的验证器可以添加必要的精度规则、规则refinement
和/或transformation
。Zod
primitives
是表示典型JavaScript
/TypeScript
primitives
(如string
、number
、boolean
等)的验证器声明方法。string
验证器通过Zod
instance
上的string()
方法声明,number
通过number()
方法声明,依此类推。Zod
object schemas
表示一个包含其所有单个属性的数据库实体。Zod
object schema
使用object()
方法初始化,并由primitives
组成。Zod
中的数据精度可以通过精度验证器API
来实现。例如,我们可以使用min()
、max()
、email()
等来实现string
的精度。Zod Schema
派生/操作可以根据需要进行,并提供完整的TypeScript
支持。例如,使用partial()
、pick()
和omit()
进行派生是很常见的。验证器
refinement
可以通过自定义需求来实现,以运行细致的表单验证。Zod
通过refine()
和superRefine()
提供refinement
功能。表单字段数据可以通过
Zod
transformations
进行转换。transform()
方法用于此目的。从
Zod Schema
生成类型是通过Schema
上的infer()
方法完成的。Zod
验证运行是通过parse()
方法执行的。运行验证会根据声明的验证器检查表单字段数据的准确性。任何错误都会返回给Zod
Schema
/表单instance
。
用于 React Hook Form 的 Zod Resolver
当将 Zod
与 React Hook Form
一起使用时,我们需要使用用于 React Hook Form
的 Zod resolver
。React Hook Form
通过其 Zod Resolver 包来支持 Zod
。Zod resolver
的工作是根据 React Hook Form
中发生的事件来触发验证,转换已执行的验证并将结果(成功或失败)返回给 React Hook Form
的 instance
。
使用 TypeScript 进行 Zod Schema 验证:如何从普通的 React Hook Form 迁移
现在,让我们来看一个使用普通 React Hook Form
的简单 React
应用程序。我们将使用这个应用程序来演示如何迁移到 Zod
进行 Schema
验证。
Create Post
表单
我们的应用程序将有一个 Create Post
表单。该表单将包含以下字段:
title
: 帖子的标题。slug
: 帖子的 slug。content
: 帖子的内容。category
: 帖子的类别。published
: 一个布尔值,用于指示帖子是否已发布。
这是 CreatePost
组件的代码:
// src/components/CreatePost.tsx import React from 'react'; import { useForm, SubmitHandler } from 'react-hook-form'; type Inputs = { title: string; slug: string; content: string; category: string; published: boolean; }; const CreatePost: React.FC = () => { const { register, handleSubmit, formState: { errors }, } = useForm<Inputs>(); const onSubmit: SubmitHandler<Inputs> = (data) => { console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <label htmlFor="title">Title</label> <input id="title" {...register('title', { required: 'Title is required' })} /> {errors.title && <p>{errors.title.message}</p>} </div> <div> <label htmlFor="slug">Slug</label> <input id="slug" {...register('slug', { required: 'Slug is required' })} /> {errors.slug && <p>{errors.slug.message}</p>} </div> <div> <label htmlFor="content">Content</label> <textarea id="content" {...register('content', { required: 'Content is required' })} /> {errors.content && <p>{errors.content.message}</p>} </div> <div> <label htmlFor="category">Category</label> <select id="category" {...register('category')}> <option value="tech">Tech</option> <option value="lifestyle">Lifestyle</option> <option value="education">Education</option> </select> </div> <div> <label htmlFor="published">Published</label> <input id="published" type="checkbox" {...register('published')} /> </div> <button type="submit">Create Post</button> </form> ); }; export default CreatePost;
在上面的代码中,我们有一个包含五个字段的简单表单。我们正在使用 React Hook Form
中的 useForm
hook 来管理表单状态。我们还使用 Inputs
类型为表单输入定义了类型。
验证是使用 register
函数中的 required
属性完成的。这是一个简单的验证,但随着表单的增长,它可能会变得复杂。
安装 Zod 和 Zod Resolver
要迁移到 Zod
,我们首先需要安装 Zod
和用于 React Hook Form
的 Zod resolver
:
npm install zod @hookform/resolvers
创建 Zod Schema
接下来,我们需要为我们的表单创建一个 Zod Schema
。该 Schema
将定义我们表单数据的形态以及每个字段的验证规则。
这是我们 Create Post
表单的 Zod Schema
:
// src/schemas/postSchema.ts import { z } from 'zod'; export const postSchema = z.object({ title: z.string().min(1, { message: 'Title is required' }), slug: z.string().min(1, { message: 'Slug is required' }), content: z.string().min(1, { message: 'Content is required' }), category: z.string(), published: z.boolean(), });
在上面的代码中,我们使用 z.object()
方法创建了一个 postSchema
。object()
方法接受一个对象,其中键是字段名,值是 Zod
验证器。
我们正在为 title
、slug
、content
和 category
字段使用 z.string()
验证器。min(1, { message: '...' })
是一个精度验证器,可确保字符串不为空。我们正在为 published
字段使用 z.boolean()
验证器。
从 Schema 生成类型
Zod
的一个很棒的功能是我们可以从 Schema
中推断出 TypeScript
类型。这可以确保我们的表单数据始终具有正确的类型。
要生成类型,我们可以使用 z.infer()
方法:
// src/schemas/postSchema.ts import { z } from 'zod'; export const postSchema = z.object({ // ... }); export type PostSchema = z.infer<typeof postSchema>;
现在,我们可以在 CreatePost
组件中使用 PostSchema
类型来代替 Inputs
类型。
在表单中使用 Zod Schema
现在我们有了 Zod Schema
,我们可以在 CreatePost
组件中使用它。我们需要从 @hookform/resolvers/zod
导入 zodResolver
并将其传递给 useForm
hook。
这是更新后的 CreatePost
组件:
// src/components/CreatePost.tsx import React from 'react'; import { useForm, SubmitHandler } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { postSchema, PostSchema } from '../schemas/postSchema'; const CreatePost: React.FC = () => { const { register, handleSubmit, formState: { errors }, } = useForm<PostSchema>({ resolver: zodResolver(postSchema), }); const onSubmit: SubmitHandler<PostSchema> = (data) => { console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> {/* ... form fields ... */} </form> ); }; export default CreatePost;
在上面的代码中,我们已将 Inputs
类型替换为我们从 Zod Schema
生成的 PostSchema
类型。我们还将 zodResolver
传递给了 useForm
hook。
zodResolver
现在将使用我们的 postSchema
来验证表单数据。我们还可以从 register
函数中删除 required
属性,因为验证现在由 Zod
处理。
这是 title
的更新后的表单字段:
<div> <label htmlFor="title">Title</label> <input id="title" {...register('title')} /> {errors.title && <p>{errors.title.message}</p>} </div>
就是这样!我们已经成功地将我们的表单迁移为使用 Zod
进行 Schema
验证。
使用 React Hook Form 的精细 Zod 功能:一个编辑个人资料的示例
在本节中,我们将使用一个 <EditProfile />
组件的表单。在此过程中,我们将探讨一些细微的 Zod Schema 功能。
这是 <EditProfile />
组件的代码:
// src/components/EditProfile.tsx // 由于在项目中找不到该文件,因此此处为占位符代码 // 在实际应用中,这里应该是 EditProfile 组件的完整代码 import React from 'react'; const EditProfile: React.FC = () => { return ( <form> {/* 表单字段将在此处 */} <button type="submit">更新个人资料</button> </form> ); }; export default EditProfile;
Zod 默认值
在 Zod 中,我们必须使用 default()
方法在验证器本身上指定默认值。像这样:
const first_name = zod .string() .min(1, { message: "名字不能为空" }) .max(50, { message: "真的有这么长吗?" }) .default("Dru"); const last_name = zod .string() .min(1, { message: "姓氏不能为空" }) .max(50, { message: "真的有这么长吗?" }) .default("Zod");
然而,这些值不会传递给 React Hook Form 的 defaultValues
属性,因此它们不会显示在表单字段中。
为了让它们显示在表单字段中,我们必须解析 Schema,然后将输出传递给 useForm()
的 defaultValues
配置:
const formInstance = useForm({ resolver: zodResolver(ProfileSchema), mode: "onChange", defaultValues: ProfileSchema.parse({}), criteriaMode: "all", shouldFocusError: true, reValidateMode: "onSubmit", });
通过将空对象传递给 ProfileSchema.parse({})
,之前使用 Zod default()
指定的默认值将被设置到它们的字段中。
Zod Schema 派生
Zod 支持 Schema 派生和相关的类型操作。例如,在我们需要将某些属性设置为 optional
的情况下,我们可以应用 partial()
方法来派生一个新类型:
const ProfileOptional = ProfileSchema.partial(); const ProfileOptionalLastName = ProfileSchema.partial({ last_name: true, }); type TProfileOptional = zod.infer<typeof ProfileOptional>; /* type TProfileOptional = { username?: string | undefined; first_name?: string | undefined; last_name?: string | undefined; email?: string | undefined; }; */ type TProfileOptionalLastName = zod.infer<typeof ProfileOptionalLastName>; /* type TProfileOptionalLastName = { username: string; first_name: string; email: string; last_name?: string | undefined; }; */
请注意,在没有传递任何参数的情况下,我们将部分标志应用于所有项目。我们可以通过将其设置为 true
来使单个字段成为可选字段。
提示
Zod Schema 派生实用工具用于操作表单 Schema。它们镜像了具有相似名称的 TypeScript 实用工具,并生成它们在 TypeScript 中表示的静态类型。
例如,pick()
生成的类型与 TypeScript Pick<>
具有相同的类型转换影响。omit()
带来的类型派生与 TypeScript Omit<>
相同。
Zod Refinements
Zod refinements 允许我们在验证规则中获得细粒度的特异性,这通常是使用 primitives 和字段精度方法无法实现的。
例如,我们强制我们的 email
字段为 email()
。并且我们希望它只是一个 Kryptonian 电子邮件,最后三个字符是 .kr
:
const email = zod .string() .email() .refine((e) => e.slice(e.length - 3).includes(".kr"), { message: "这应该是一个氪星人的电子邮件", }) .default("general.zod@candor.mil.kr");
提示
refine()
方法是实现非常具体的验证规则的便捷方法。可以使用 superRefine()
方法定义更详细的验证器。
有关详细信息,请参阅此处的文档。
Zod Transformations
Zod transformations 允许我们转换表单字段的值。
例如,在 username
字段中,我们希望将
(空格)转换成 _
(下划线):
const ProfileSchema = zod.object({ username: zod .string() .transform((u) => u.split(" ").join("_")) .default("general_zod"), first_name, last_name, email, });
在此代码段中,我们正在转换输入,以便所有空格都被下划线替换。转换后的数据存储在 formInstance
中。我们已在 handleSubmit
回调中将表单数据记录到控制台。您可以在提交表单后查看控制台时验证这一点。username
字段不包含任何空格——因为它们都已转换为下划线:
{ "username": "general_dru_zod", "first_name": "Dru", "last_name": "Zod", "email": "general.zod@candor.mil.kr" }
如您所见,Zod refinements 和 transformations 对于轻松实现从头开始难以实现的细微表单要求非常有用。
总结
在本文中,我们学习了如何在基于 React Hook Form 的应用程序中实现 Zod Schema 验证。
我们首先探讨了什么是 Schema 验证,为什么需要它们,以及 Zod 如何通过 Zod Resolver 为 React Hook Form 应用程序中的类型安全 Schema 提供经过实战检验的解决方案。
通过迁移现有 Create Post
基于 React Hook Form 的验证的示例,我们学习了如何使用 string
primitives 声明 Zod 验证器并从中组合对象 Schema。我们看到了使用与 string
相关的字段精度验证器(例如 min()
、max()
和 email()
)的示例。我们还了解了 Zod 如何使用 infer()
从 Schema 生成 TypeScript 类型,以及如何使用 parse()
和 safeParse()
方法执行自定义解析。
稍后,我们实现了一些通过 Zod 可以轻松实现的细微表单功能。我们使用 default()
方法以 Zod 的方式添加了默认值。借助 refine()
API,我们实现了一个自定义验证器,该验证器强制 email()
字段属于 .kr
。最后,我们学习了 transform()
方法如何帮助我们将字段数据转换为我们喜欢的内容以包含在表单数据集中。