react-hook-form

useWatch
useWatch
是 react-hook-form
提供的一个 Hook,用于实时监听表单字段的变化,而不会触发重新渲染。它的常见用途包括:
- 监听单个字段 或 多个字段 的值。
- 监听整个表单 的变化。
- 根据表单输入实时更新 UI,如动态计算、联动效果等。
1. 基本用法(监听单个字段)
tsx
import React from "react";
import { useForm, useWatch } from "react-hook-form";
const MyForm = () => {
const { register, control, handleSubmit } = useForm();
const name = useWatch({ control, name: "name" }); // 监听 name 字段
const onSubmit = (data: any) => {
console.log("提交数据:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>姓名:</label>
<input {...register("name")} />
<p>实时显示: {name}</p> {/* 监听 name 的变化,实时显示 */}
<button type="submit">提交</button>
</form>
);
};
export default MyForm;
📌 解析
useWatch({ control, name: "name" })
:监听name
字段的值,实时显示到页面上。useWatch
不会引起组件重新渲染,提高性能。
2. 监听多个字段
tsx
const MyForm = () => {
const { register, control, handleSubmit } = useForm();
const [name, email] = useWatch({ control, name: ["name", "email"] }); // 监听多个字段
const onSubmit = (data: any) => {
console.log("提交数据:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} placeholder="姓名" />
<input {...register("email")} placeholder="邮箱" />
<p>姓名: {name}</p>
<p>邮箱: {email}</p>
<button type="submit">提交</button>
</form>
);
};
📌 解析
useWatch({ control, name: ["name", "email"] })
监听多个字段。
3. 监听整个表单
tsx
const MyForm = () => {
const { register, control, handleSubmit } = useForm();
const formValues = useWatch({ control }); // 监听整个表单
const onSubmit = (data: any) => {
console.log("提交数据:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} placeholder="姓名" />
<input {...register("email")} placeholder="邮箱" />
<input {...register("age")} placeholder="年龄" />
<pre>{JSON.stringify(formValues, null, 2)}</pre> {/* 实时显示所有数据 */}
<button type="submit">提交</button>
</form>
);
};
📌 解析
useWatch({ control })
监听整个表单的所有字段,表单的值会 实时显示。
4. 监听嵌套字段
如果表单有嵌套数据结构(如 address.city
),也可以使用 useWatch
监听。
tsx
const MyForm = () => {
const { register, control, handleSubmit } = useForm({
defaultValues: {
address: {
city: "",
},
},
});
const city = useWatch({ control, name: "address.city" }); // 监听嵌套字段
const onSubmit = (data: any) => {
console.log("提交数据:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>城市:</label>
<input {...register("address.city")} />
<p>当前城市: {city}</p> {/* 实时显示城市的值 */}
<button type="submit">提交</button>
</form>
);
};
📌 解析
register("address.city")
绑定嵌套字段。useWatch({ control, name: "address.city" })
监听address.city
。
5. useWatch
和 useEffect
联动
在某些情况下,我们可能希望在字段变化时 执行某些副作用,例如自动填充或计算。
tsx
import React, { useEffect } from "react";
import { useForm, useWatch } from "react-hook-form";
const MyForm = () => {
const { register, control, setValue, handleSubmit } = useForm({
defaultValues: { price: "", quantity: "", total: "" },
});
const [price, quantity] = useWatch({ control, name: ["price", "quantity"] });
useEffect(() => {
if (price && quantity) {
setValue("total", (parseFloat(price) * parseInt(quantity)).toFixed(2));
}
}, [price, quantity, setValue]);
const onSubmit = (data: any) => {
console.log("提交数据:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("price")} placeholder="单价" type="number" />
<input {...register("quantity")} placeholder="数量" type="number" />
<input {...register("total")} placeholder="总价" readOnly />
<button type="submit">提交</button>
</form>
);
};
export default MyForm;
📌 解析
useWatch
监听price
和quantity
,实时计算total
。useEffect
监听price
和quantity
变化,并使用setValue("total", 计算结果)
自动更新total
。
6. useWatch
vs watch
特性 | watch |
useWatch |
---|---|---|
实时监听 | ✅ | ✅ |
自动 re-render | ❌ 不会触发 | ✅ 只影响 useWatch |
适合大表单 | ❌(可能影响性能) | ✅(不会触发全局 re-render) |
什么时候用 useWatch
?
- 性能优化:如果只想监听某个字段,而不希望整个表单重新渲染。
- 嵌套组件监听:可以在某个子组件中监听父组件表单的数据。
什么时候用 watch
?
- 临时获取当前值,但不影响 UI,例如:
tsx
const { watch } = useForm(); console.log(watch("name")); // 只在组件渲染时打印当前值
- 整个组件都需要值变化时。
总结
useWatch
不会导致整个组件重新渲染,适合优化性能。- 可以监听 单个字段、多字段、整个表单,甚至嵌套字段。
- 可以配合
useEffect
进行计算、填充默认值等操作。
二、UseFormReturn
:表单控制与状态管理
定义:
UseFormReturn
是 useForm
Hook 返回的对象类型,包含表单控制、状态及操作方法9。
核心属性与方法:
-
控制对象
typescriptconst { control, register, handleSubmit, formState: { errors } } = useForm<FormData>();
control
:传递给useWatch
或Controller
以实现受控组件绑定。register
:注册原生输入组件(如<input>
)。formState
:包含errors
、isDirty
等状态信息。
-
提交与验证
tsxconst onSubmit: SubmitHandler<FormData> = (data) => console.log(data); <form onSubmit={handleSubmit(onSubmit)}>
-
动态操作
类型定义(TS):
typescript
type UseFormReturn<T> = {
control: Control<T>;
register: UseFormRegister<T>;
handleSubmit: (callback: SubmitHandler<T>) => (e?: React.BaseSyntheticEvent) => Promise<void>;
// 其他属性...
};
Controller
的基本用法
Controller
主要用于受控组件,它的作用是让 react-hook-form
管理非原生输入组件(如 Select
、Checkbox
、DatePicker
等)。
1️⃣ 基本用法
用 Controller
绑定 input
tsx
import React from "react";
import { useForm, Controller } from "react-hook-form";
const MyForm = () => {
const { control, handleSubmit } = useForm();
const onSubmit = (data) => {
console.log("表单数据:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="username"
control={control}
defaultValue=""
render={({ field }) => <input {...field} placeholder="输入用户名" />}
/>
<button type="submit">提交</button>
</form>
);
};
export default MyForm;
🔹 解析
name="username"
→ 表单字段名control={control}
→ 让react-hook-form
管理它defaultValue=""
→ 初始值render={({ field }) => <input {...field} />}
→ 绑定value
和onChange
2️⃣ 监听多个字段
tsx
<Controller
name="firstName"
control={control}
defaultValue=""
render={({ field }) => <input {...field} placeholder="名字" />}
/>
<Controller
name="lastName"
control={control}
defaultValue=""
render={({ field }) => <input {...field} placeholder="姓氏" />}
/>
- 可以在
handleSubmit
里获取{ firstName, lastName }
3️⃣ 添加校验规则
tsx
<Controller
name="email"
control={control}
defaultValue=""
rules={{ required: "邮箱必填", pattern: { value: /^\S+@\S+$/, message: "邮箱格式错误" } }}
render={({ field, fieldState }) => (
<div>
<input {...field} placeholder="输入邮箱" />
{fieldState.error && <p style={{ color: "red" }}>{fieldState.error.message}</p>}
</div>
)}
/>
rules
里设置required
必填 和pattern
格式校验fieldState.error.message
显示错误信息
4️⃣ 绑定 checkbox
tsx
<Controller
name="agree"
control={control}
defaultValue={false}
render={({ field }) => <input type="checkbox" {...field} />}
/>
type="checkbox"
适用于复选框
5️⃣ 绑定 select
tsx
<Controller
name="gender"
control={control}
defaultValue=""
render={({ field }) => (
<select {...field}>
<option value="">请选择</option>
<option value="male">男</option>
<option value="female">女</option>
</select>
)}
/>
- 绑定
<select>
,动态管理选项值
6️⃣ useWatch
监听 Controller
tsx
import { useWatch } from "react-hook-form";
const gender = useWatch({ control, name: "gender" });
console.log("当前选择:", gender);
- 监听
gender
的值
🎯 总结
✅ Controller
适用于非原生输入(如 Select
、Checkbox
)。
✅ render={({ field }) => ...}
让 react-hook-form
控制组件。
✅ 可结合 rules
进行表单验证。
✅ 可搭配 useWatch
实时监听输入值。
🚀 如果你的组件是受控的,就用 Controller
!
一、错误消息的国际化
-
与 i18n 库深度集成
使用react-i18next
等库管理翻译资源,通过useTranslation
Hook 动态获取错误消息,结合useForm
的errors
对象展示多语言提示。tsxconst { t } = useTranslation(); const { register, formState: { errors } } = useForm(); <input {...register("email", { required: t("form.errors.email.required") // 从翻译文件获取错误文本 })} /> {errors.email && <span>{errors.email.message}</span>}
-
动态切换语言时的错误更新
监听语言切换事件,通过useEffect
或FormProvider
强制重新渲染表单,确保错误提示实时更新:tsxconst { i18n } = useTranslation(); const { reset } = useForm(); useEffect(() => { reset(); // 重置表单以触发验证规则重新计算 }, [i18n.language]);
二、验证规则的动态配置
-
基于语言的验证规则切换
使用 Yup、Zod 等验证库的上下文(context
)传递当前语言参数,生成动态验证规则:tsxconst { t, i18n } = useTranslation(); const schema = yup.object({ phone: yup .string() .required(t("errors.required")) .matches(/^[0-9]+$/, t("errors.phone.invalid")) }); const { handleSubmit } = useForm({ resolver: yupResolver(schema), context: { lang: i18n.language } // 传递语言参数 });
-
异步加载翻译验证规则
针对大型表单,按需加载不同语言的验证配置:tsxconst loadRules = async (lang) => { const rules = await import(`../locales/${lang}/validationRules`); return rules; };
三、表单标签与占位符的本地化
-
动态渲染标签和占位符
从翻译文件中提取字段标签和占位符,结合Controller
或原生输入组件实现:tsx<Controller name="username" render={({ field }) => ( <Input {...field} label={t("form.labels.username")} placeholder={t("form.placeholders.username")} /> )} />
-
处理复数与变量插值
利用 i18n 的插值功能处理动态内容:tsxregister("discountCode", { validate: (value) => value === t("promoCode", { count: 1 }) // 根据语言处理复数 });
四、性能优化与状态管理
-
隔离渲染范围
使用useWatch
或FormProvider
按需订阅字段更新,避免语言切换时全局重渲染:tsxconst FormProviderWrapper = ({ children }) => ( <FormProvider {...methods}>{children}</FormProvider> );
-
缓存翻译资源
通过i18next-http-backend
预加载翻译文件,减少切换语言时的延迟:tsxi18n.use(Backend).init({ backend: { loadPath: "/locales/{{lng}}/{{ns}}.json" } });
参考实现:6
五、处理复杂本地化场景
-
日期与货币格式
结合react-intl
或date-fns
本地化日期输入:tsx<Controller name="birthDate" render={({ field }) => ( <DatePicker value={field.value} onChange={field.onChange} locale={i18n.language} // 动态设置日期格式 /> )} />
-
多语言表单字段的动态增减
针对需要按语言动态增减字段的表单(如地址输入差异),使用useFieldArray
管理动态字段:tsxconst { fields, append } = useFieldArray({ control, name: "address" }); useEffect(() => { if (i18n.language === "zh") append({ province: "", city: "" }); // 中文地址需省份字段 }, [i18n.language]);
总结
技巧 | 适用场景 | 工具/库 |
---|---|---|
错误消息翻译 | 多语言错误提示需求 | react-i18next + useForm 69 |
动态验证规则 | 不同语言的验证逻辑差异 | Yup/Zod + 上下文参数 39 |
标签占位符本地化 | 表单字段文本需多语言展示 | Controller + 翻译文件 612 |
隔离渲染优化 | 大型表单性能敏感场景 | FormProvider + useWatch 57 |
FormProvider
React 中使用 react-hook-form
,包括 useForm
和 FormProvider
,并展示验证和动态字段的场景。
FormProvider
是 react-hook-form
提供的一个组件,主要用于在嵌套的组件树中共享表单状态。它可以让子组件通过 useFormContext
访问父组件的 useForm
实例,而不需要将 useForm
的返回值传递给每一个子组件。
FormProvider
的使用步骤:
- 在父组件中使用
useForm
初始化表单,然后将useForm
返回的对象通过FormProvider
传递给子组件。 - 在子组件中使用
useFormContext
获取表单的方法,比如register
、handleSubmit
、errors
等。
示例代码
1. 父组件:使用 FormProvider
包裹子组件
tsx
import React from 'react';
import { useForm, FormProvider } from 'react-hook-form';
import { InputField } from './InputField'; // 子组件
const MyForm = () => {
// 初始化 useForm,并提供表单默认值
const methods = useForm({
defaultValues: {
name: '',
email: '',
},
});
// 提交表单时的回调函数
const onSubmit = (data: any) => {
console.log("提交的数据:", data);
};
return (
<FormProvider {...methods}> {/* 通过 FormProvider 传递表单方法给子组件 */}
<form onSubmit={methods.handleSubmit(onSubmit)}>
<h2>用户信息表单</h2>
{/* 渲染子组件,用于输入字段 */}
<InputField name="name" label="姓名" />
<InputField name="email" label="邮箱" />
<button type="submit">提交</button>
</form>
</FormProvider>
);
};
export default MyForm;
- 通过
FormProvider {...methods}
将表单的所有方法传递给子组件,methods
是useForm
返回的对象。 - 在表单提交时,
methods.handleSubmit(onSubmit)
会处理验证并提交数据。
2. 子组件:使用 useFormContext
获取表单方法
tsx
import React from 'react';
import { useFormContext } from 'react-hook-form';
interface InputFieldProps {
name: string;
label: string;
}
export const InputField: React.FC<InputFieldProps> = ({ name, label }) => {
// 从 FormProvider 获取表单方法
const { register, formState: { errors } } = useFormContext();
return (
<div>
<label>{label}</label>
<input
{...register(name, { required: `${label} 是必填项` })} // 使用 register 注册输入框
/>
{/* 显示验证错误信息 */}
{errors[name] && <p style={{ color: 'red' }}>{errors[name]?.message}</p>}
</div>
);
};
useFormContext()
获取到父组件FormProvider
中的表单方法,这样就可以在InputField
中使用register
、formState
等。- 通过
register
绑定字段,并添加验证规则。 errors[name]
用于显示字段的错误信息。
总结
FormProvider
用于提供表单的上下文,允许嵌套组件访问useForm
的方法。- 子组件使用
useFormContext
获取表单状态,避免逐一传递useForm
的返回值。 - 这种方式尤其适合表单结构复杂或包含多个嵌套组件的情况。