在React中使用TypeScript和Zod構(gòu)建自定義表單處理方案
React 提供了许多库,例如 Formik、React Hook Form 和 Redux Form,用于管理表单。然而,构建自定义表单解决方案在某些情况下更为有利。本文将重点介绍如何使用 TypeScript 和 Zod 构建一个可重用且类型安全的表单解决方案,并进行模式验证。
对为什么要进行自定义表单管理感到好奇?- 轻量且灵活:避免让应用程序因引入大型库而变得臃肿。
- 细粒度控制:只需实现你需要的功能。
- 全面的类型安全:集成 TypeScript 可以帮助你在开发过程中捕捉错误。
- 自定义功能:根据你的使用场景添加特定逻辑。
Zod 是一个以 TypeScript 优先的模式验证库。它帮助定义和验证数据结构,提供了一个简洁直观的 API。
一个步骤指南:一步一步的指南: 1. 建立项目首先,安装必要的依赖项:
npm install zod react
npm install --save-dev @types/react
2. 使用 Zod 定义表单模式。
import { z } from "zod";
const userSchema = z.object({
name: z.string().min(1, "名字是必填项"),
email: z.string().email("无效的电子邮件地址"),
age: z.number().min(18, "必须年满18岁"),
});
// 推断 TypeScript 类型
type UserFormValues = z.infer<typeof userSchema>;
这个方案确保了:
name
是一个非空的字符串。email
必须是有效的电子邮件地址。age
必须是数字,且至少为 18。
```jsx
// 导入React的useState和Zod的ZodError
import { useState } from "react";
import { ZodError } from "zod";
// 定义一个useForm的函数,接受一个模式schema和初始值initialValues
export function useForm<T>(schema: z.ZodSchema<T>, initialValues: T) {
// 使用useState来跟踪表单的值和错误
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
// 定义一个handleChange的函数,用于处理表单输入变化
const handleChange = (key: keyof T) => (event: React.ChangeEvent<HTMLInputElement>) => {
setValues({ ...values, [key]: event.target.value });
};
// 定义一个validate的函数,用于验证表单数据
const validate = () => {
try {
// 使用schema进行验证
schema.parse(values);
// 如果验证通过,清除错误信息
setErrors({});
return true;
} catch (e) {
// 如果验证失败,检查是否是ZodError
if (e instanceof ZodError) {
// 获取错误信息
const fieldErrors = e.errors.reduce((acc, error) => {
// 将错误信息存储在acc中
acc[error.path[0] as keyof T] = error.message;
return acc;
}, {} as Partial<Record<keyof T, string>>);
// 设置错误信息
setErrors(fieldErrors);
}
return false;
}
};
// 定义一个handleSubmit的函数,用于处理表单提交
const handleSubmit = (onSubmit: (values: T) => void) => (event: React.FormEvent) => {
// 阻止表单的默认行为
event.preventDefault();
// 如果验证通过,则调用onSubmit函数
if (validate()) {
onSubmit(values);
}
};
// 返回值,错误,handleChange和handleSubmit
return {
values,
errors,
handleChange,
handleSubmit,
};
}
## 4\. 创建 React 组件
import React from "react";
import { useForm } from "./useForm";
import { userSchema } from "./schemas";
const UserForm = () => {
const initialValues = { name: "", email: "", age: 18 };
const { values, errors, handleChange, handleSubmit } = useForm(userSchema, initialValues);
const onSubmit = (data: typeof initialValues) => {
console.log("已提交:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>名字</label>
<input
type="text"
value={values.name}
onChange={handleChange("name")}
/>
{errors.name && <p>{errors.name}</p>}
</div>
<div>
<label>电子邮件</label>
<input
type="email"
value={values.email}
onChange={handleChange("email")}
/>
{errors.email && <p>{errors.email}</p>}
</div>
<div>
<label>年纪</label>
<input
type="number"
value={values.age}
onChange={handleChange("age")}
/>
{errors.age && <p>{errors.age}</p>}
</div>
<button type="submit">提交按钮</button>
</form>
);
};
export default UserForm;
## 如下关键特性
1. **动态错误处理**:错误会显示在其相应的字段附近,以提升用户体验。
2. **可重复使用的逻辑**:`useForm` 钩子可以处理任何通过 Zod 定义的表单模式。
3. **全面类型保障**:Zod 确保表单数据符合模式,从而减少运行时错误。
## 5\. 添加一些高级特性
* **异步验证(例如,检查邮箱是否已被注册)**:用于服务器端验证。
* **动态字段**:根据用户行为动态添加或删除表单字段。
* **与库的集成**:使用 Tailwind 进行样式设计,或添加 Toast 通知以增强用户体验。
例子:添加异步校验
## 第一步:更新架构
添加一个名为 `refine` 的功能来对 `email` 字段执行异步验证。
import { z } from "zod";
// 检查邮箱是否存在的模拟 API 调用
const checkEmailExists = async (email: string) => {
return new Promise<boolean>((resolve) => {
setTimeout(() => {
resolve(email === "test@example.com"); // 模拟一个存在的邮箱
}, 1000);
});
};
// 带邮箱验证的异步模式
const asyncSchema = z.object({
name: z.string().min(1, "名字是必填项"),
email: z
.string()
.email("无效的邮箱地址")
.refine(
async (email) => !(await checkEmailExists(email)),
"邮箱已被使用"
),
password: z.string().optional(),
confirmPassword: z
.string()
.optional()
.refine(
(value, ctx) => {
if (!value && ctx.parent.password) {
return false;
}
return value === ctx.parent.password;
},
"密码必须一致"
),
});
export type AsyncFormValues = z.infer<typeof asyncSchema>;
步骤 2:更新 `useForm` 钩子函数
为了支持动态字段,我们可以通过模式校验来进行条件更新。
导入 { useState } from "react";
导入 { ZodError } from "zod";
出口函数 useForm<T>(schema: z.ZodSchema<T>, initialValues: T) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
const [loading, setLoading] = useState(false);
const handleChange = (key: keyof T) => async (event: React.ChangeEvent<HTMLInputElement>) => {
const updatedValues = { ...values, [key]: event.target.value };
setValues(updatedValues);
如果 (key === "email") {
setLoading(true);
尝试 {
等待 schema.parseAsync(updatedValues);
setErrors({});
} 捕获 (e) {
如果 (e instanceof ZodError) {
const fieldErrors = e.errors.reduce((acc, error) => {
acc[error.path[0] as keyof T] = error.message;
返回 acc;
}, {} as Partial<Record<keyof T, string>>);
setErrors(fieldErrors);
}
}
setLoading(false);
}
};
const validate = async () => {
尝试 {
等待 schema.parseAsync(values);
setErrors({});
返回 true;
} 捕获 (e) {
如果 (e instanceof ZodError) {
const fieldErrors = e.errors.reduce((acc, error) => {
acc[error.path[0] as keyof T] = error.message;
返回 acc;
}, {} as Partial<Record<keyof T, string>>);
setErrors(fieldErrors);
}
返回 false;
}
};
const handleSubmit = (onSubmit: (values: T) => void) => async (event: React.FormEvent) => {
event.preventDefault();
如果 (await validate()) {
onSubmit(values);
}
};
返回 {
values,
errors,
loading,
handleChange,
handleSubmit,
};
}
### 步骤 3:创建动态表单
使用更新后的 `useForm` 钩子来创建一个动态表单,字段根据条件显示。
import React from "react";
import { useForm } from "./useForm";
import { asyncSchema, AsyncFormValues } from "./schemas";
const DynamicForm = () => {
const initialValues: AsyncFormValues = {
name: "",
email: "",
password: "",
confirmPassword: "",
};
const { values, errors, loading, handleChange, handleSubmit } = useForm(asyncSchema, initialValues);
const onSubmit = (data: AsyncFormValues) => {
console.log("提交表单:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>姓名:</label>
<input
type="text"
value={values.name}
onChange={handleChange("name")}
/>
{errors.name && <p>错误信息: {errors.name}</p>}
</div>
<div>
<label>邮箱</label>
<input
type="email"
value={values.email}
onChange={handleChange("email")}
/>
{loading && <p>正在验证邮箱...</p>}
{errors.email && <p>错误信息: {errors.email}</p>}
</div>
{values.email && !errors.email && (
<>
<div>
<label>密码</label>
<input
type="password"
value={values.password}
onChange={handleChange("password")}
/>
</div>
<div>
<label>确认密码</label>
<input
type="password"
value={values.confirmPassword}
onChange={handleChange("confirmPassword")}
/>
{errors.confirmPassword && <p>错误信息: {errors.confirmPassword}</p>}
</div>
</>
)}
<button type="submit" disabled={loading}>
提交表单
</button>
</form>
);
};
export default DynamicForm;
# 它是怎么工作的
**动态字段显示**:
* 只有在电子邮件验证成功之后,才会显示密码框和确认密码框。
* 这样可以改善使用体验,减少用户认知负担。
**异步校验** :
* 通过 `checkEmailExists` 异步地进行邮箱验证。
**异常处理**
每个字段的错误会在上下文中显示。
# 结论部分
用 React、TypeScript 和 Zod 构建自定义表单解决方案提供轻量且灵活的处理,并具备类型安全功能。虽然像 Formik 和 React Hook Form 这样的库非常出色,但自定义解决方案在需要完全控制表单时更占优势。
[来杯咖啡支持我](https://buymeacoffee.com/guestdm)
共同學(xué)習(xí),寫下你的評論
評論加載中...
作者其他優(yōu)質(zhì)文章