nextjs 使用react-hook-form和zod实现登录表单验证和提交

背景说明

nextjs 使用中不可避免地会碰到使用表单,尤其是登录表单。而对于提交表单来说要关注的几个要素就是数据收集、数据校验和数据提交(涉及异步操作)。本文将围绕这几个要素,并通过zodreact-hook-form提供的能力实现登录表单。

本文所述依赖如下的库包及其版本

包名 版本号
next 14.2.15
react 18.2.0
react-dom 18.2.0
zod 3.24.1
react-hook-form 7.54.2
@hookform/resolvers 3.9.1

本文的开发环境基于 Macbook Pro M1 MacOS 14.6.1。

zod 简单上手

zod 提供了简单的表单校验器,且支持typescript,并弥补了typescrip对运行时代码无法校验的不足,通过一次定义,不仅可以通过safeParse方法验证表单数据,导出错误,还可以通过z.infer推断表单模型的typescript类型。 下载

bash 复制代码
pnpm add zod

以邮箱登录表单为例

ts 复制代码
export const loginSchema = z.object({
    email: z.string().email("邮箱地址格式错误").min(1, "邮箱地址不能为空"),
    password: z.string().min(1, "密码不能为空")
})

export type LoginData = z.infer<typeof loginSchema>

可以看到,我们可以使用z.object()定义模型,通过schema.safeParse()方法返回的对象resp。 通过resp.success可以判断数据是否校验成功,一旦校验成功则通过resp.data获取输入的表单数据,否则可以使用resp.error获取校验错误。

登录表单的UI实现

由于css样式比较繁琐,这里使用shadcn ui这个组件库+Tailwindcss。其中有邮箱和密码的输入框还有一个用于提交表单的按钮。

ts 复制代码
export default function LoginForm() {
  return (
    <form>
      <div>
        <Label htmlFor="login-email">邮箱:</Label>
        <Input
          id="login-email"
          type="text"
          autoComplete="off"
          placeholder="请输入您的邮箱地址"
          className={cn(
            "border",
            "focus-visible:!outline-none focus-visible:ring-1 focus-visible:ring-gray-500"
          )}
        />
      </div>
      <div>
        <Label htmlFor="login-pwd">密码:</Label>
        <Input
          id="login-pwd"
          type="password"
          autoComplete="new-password"
          placeholder="请输入密码:"
          className={cn(
            "border",
            "focus-visible:!outline-none focus-visible:ring-1 focus-visible:ring-gray-500"
          )}
        />
      </div>
      <Button className="w-full">登录</Button>
    </form>
  );
}

使用 react-hook-form

基本使用

react-hook-form 提供了许多用于表单的hook,在这使用的是useForm这个hook。 基本用法如下:

tsx 复制代码
import {useForm} from "react-hook-form"
export default function Page(){
const {
    handleSubmit,
    formState: { errors, isSubmitting },
    reset,
    register,
  } = useForm();
    return <form onSubmit={handleSubmit((data)=>{
        console.log("data",data)
    })}>
    <Label htmlFor="login-email">邮箱:</Label>
    <Input
          id="login-email"
          type="text"
          autoComplete="off"
          placeholder="请输入您的邮箱地址"
          {...register("email")}
          className={cn(
            "border",
            "focus-visible:!outline-none focus-visible:ring-1 focus-visible:ring-gray-500"
          )}
        />
    </form>
}

可以看到useForm导出了三个方法和一个对象,其中最为重要的当属handleSubmitregister方法,一个用来注册某些原生属性或者事件以及校验规则。对于校验规则,其提供了一个resolver属性允许使用第三方的表单校验库去完成。

tsx 复制代码
import { zodResolver } from "@hookform/resolvers/zod";
...
const {
    handleSubmit,
    formState: { errors, isSubmitting },
    reset,
    register,
  } = useForm({ resolver: zodResolver(loginSchema) });

对此,我们可以使用上节提到的zod来实现表单校验,通过安装@hookform/resolvers导入zodResolver,传入上节定义号的表单校验模型。

ts智能提示

再通过useForm传入zod推断的数据类型,可以在使用register方法时得到更好的智能提示。

tsx 复制代码
type LoginData = z.infer<typeof loginSchema>
useForm<LoginData>()

获取错误并修改表单样式

当表单校验发生错误时,常见的UI提示是输入框边框转为警告色,并在其下方显示对应的错误信息,就像下述这样,当邮箱地址格式错误显示错误的信息:

该错误信息在使用zod时已定义:

警告色样式可由tailwindcss提供:

但如何知道字段校验错误,并取出这些错误提示信息呢。答案是上文导出的errors属性。

tsx 复制代码
const {formState:{errors}} = useForm()

导出errors属性,其也是一个对象,可通过errors.[field].message取出错误信息

tsx 复制代码
console.log(errors.email.message) \\邮箱地址格式错误

可以使用一个p标签显示信息

tsx 复制代码
<form>
    <Label htmlFor="login-email">邮箱:</Label>
    <Input
            id="login-email"
            type="text"
            {...register("email")}
            autoComplete="off"
            placeholder="请输入您的邮箱地址"
            className={cn(
              "border",
              "focus-visible:!outline-none focus-visible:ring-1 focus-visible:ring-gray-500",
              errors.email
                ? "border-red-500 focus-visible:!ring-red-700"
                : "border-gray-300 focus-visible:ring-gray-500"
            )}
          />
          {errors.email && (
            <p className="text-red-500 text-sm">{errors.email.message}</p>
          )}
</form>

至此,实现了字段校验错误信息的显示和UI的警告色功能。

表单提交时按钮禁用

当表单在提交时往往是异步状态,在提交结束前我们往往期望暂时禁用提交按钮,直到本次提交完成(无论成功与否)。

在当前的示例中,点击登录按钮,按钮立刻处于禁用样式其文字页变更为【登录中...】,很容易想到需要一个布尔值在表单提交时设置为true添加到buttondisabled属性和实现添加渲染不同的按钮文字。 幸运地是,useForm提供了这个属性,我们可以像得到errors对象属性一样,拿到这个布尔属性isSubmitting

tsx 复制代码
const {
    handleSubmit,
    formState: { errors, isSubmitting },
    reset,
    register,
  } = useForm<LoginData>({ resolver: zodResolver(loginSchema) });
  
  return (
      ...
      <Button className="w-full" disabled={isSubmitting}>
            {isSubmitting ? "登录中..." : "登录"}
          </Button>
      ...
  )

到这,按钮提交时禁用也实现了。

当本示例的登录表单完成校验后将会触发form元素的提交事件,执行在handleSubmit中传入的回调函数:

本文小结

本文通过登录表单的案例,介绍了如何使用zodreact-hook-form实现表单数据收集和校验的全过程和数据校验,并补充了一些需要的注意事项。

相关推荐
coderHing[专注前端]6 小时前
告别 try/catch 地狱:用三元组重新定义 JavaScript 错误处理
开发语言·前端·javascript·react.js·前端框架·ecmascript
KAI7 小时前
Next项目中静态资源的压缩、优化
next.js
代码小学僧7 小时前
从 Arco Table 迁移到 VTable:VTable使用经验分享
前端·react.js·开源
San308 小时前
深度驱动:React Hooks 核心之 `useState` 与 `useEffect` 实战详解
javascript·react.js·响应式编程
@大迁世界8 小时前
面了 100+ 次前端后,我被一个 React 问题当场“打回原形”
前端·javascript·react.js·前端框架·ecmascript
骑驴看星星a9 小时前
【回顾React的一些小细节】render里不可包含的东西
前端·javascript·react.js
San30.9 小时前
现代前端工程化实战:从 Vite 到 React Router demo的构建之旅
前端·react.js·前端框架
烟袅10 小时前
深入理解 React 中 useState 与 useEffect
前端·javascript·react.js
小白阿龙10 小时前
脚手架启动失败(Vue CLI/Vite/Create React App)
前端·vue.js·react.js