react-hook-form

15 分钟
68 阅读
react-hook-form

useWatch

useWatchreact-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. useWatchuseEffect 联动

在某些情况下,我们可能希望在字段变化时 执行某些副作用,例如自动填充或计算。

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 监听 pricequantity,实时计算 total
  • useEffect 监听 pricequantity 变化,并使用 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:表单控制与状态管理

定义
UseFormReturnuseForm Hook 返回的对象类型,包含表单控制、状态及操作方法9

核心属性与方法

  1. 控制对象

    typescript 复制代码
    const { control, register, handleSubmit, formState: { errors } } = useForm<FormData>();
    • control:传递给 useWatchController 以实现受控组件绑定。
    • register:注册原生输入组件(如 <input>)。
    • formState:包含 errorsisDirty 等状态信息。
  2. 提交与验证

    tsx 复制代码
    const onSubmit: SubmitHandler<FormData> = (data) => console.log(data);
    <form onSubmit={handleSubmit(onSubmit)}>
  3. 动态操作

    • setValue('field', value):手动设置字段值。
    • reset():重置表单至初始状态59

类型定义(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 管理非原生输入组件(如 SelectCheckboxDatePicker 等)。


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} />} → 绑定 valueonChange

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 适用于非原生输入(如 SelectCheckbox)。
render={({ field }) => ...}react-hook-form 控制组件。
✅ 可结合 rules 进行表单验证
✅ 可搭配 useWatch 实时监听输入值

🚀 如果你的组件是受控的,就用 Controller


一、错误消息的国际化

  1. 与 i18n 库深度集成
    使用 react-i18next 等库管理翻译资源,通过 useTranslation Hook 动态获取错误消息,结合 useFormerrors 对象展示多语言提示。

    tsx 复制代码
    const { t } = useTranslation();
    const { register, formState: { errors } } = useForm();
    
    <input
      {...register("email", {
        required: t("form.errors.email.required") // 从翻译文件获取错误文本
      })}
    />
    {errors.email && <span>{errors.email.message}</span>}

    参考实现69

  2. 动态切换语言时的错误更新
    监听语言切换事件,通过 useEffectFormProvider 强制重新渲染表单,确保错误提示实时更新:

    tsx 复制代码
    const { i18n } = useTranslation();
    const { reset } = useForm();
    
    useEffect(() => {
      reset(); // 重置表单以触发验证规则重新计算
    }, [i18n.language]);

二、验证规则的动态配置

  1. 基于语言的验证规则切换
    使用 Yup、Zod 等验证库的上下文(context)传递当前语言参数,生成动态验证规则:

    tsx 复制代码
    const { 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 } // 传递语言参数
    });

    参考实现39

  2. 异步加载翻译验证规则
    针对大型表单,按需加载不同语言的验证配置:

    tsx 复制代码
    const loadRules = async (lang) => {
      const rules = await import(`../locales/${lang}/validationRules`);
      return rules;
    };

三、表单标签与占位符的本地化

  1. 动态渲染标签和占位符
    从翻译文件中提取字段标签和占位符,结合 Controller 或原生输入组件实现:

    tsx 复制代码
    <Controller
      name="username"
      render={({ field }) => (
        <Input
          {...field}
          label={t("form.labels.username")}
          placeholder={t("form.placeholders.username")}
        />
      )}
    />

    参考实现612

  2. 处理复数与变量插值
    利用 i18n 的插值功能处理动态内容:

    tsx 复制代码
    register("discountCode", {
      validate: (value) => value === t("promoCode", { count: 1 }) // 根据语言处理复数
    });

四、性能优化与状态管理

  1. 隔离渲染范围
    使用 useWatchFormProvider 按需订阅字段更新,避免语言切换时全局重渲染:

    tsx 复制代码
    const FormProviderWrapper = ({ children }) => (
      <FormProvider {...methods}>{children}</FormProvider>
    );

    参考实现57

  2. 缓存翻译资源
    通过 i18next-http-backend 预加载翻译文件,减少切换语言时的延迟:

    tsx 复制代码
    i18n.use(Backend).init({
      backend: { loadPath: "/locales/{{lng}}/{{ns}}.json" }
    });

    参考实现6


五、处理复杂本地化场景

  1. 日期与货币格式
    结合 react-intldate-fns 本地化日期输入:

    tsx 复制代码
    <Controller
      name="birthDate"
      render={({ field }) => (
        <DatePicker
          value={field.value}
          onChange={field.onChange}
          locale={i18n.language} // 动态设置日期格式
        />
      )}
    />
  2. 多语言表单字段的动态增减
    针对需要按语言动态增减字段的表单(如地址输入差异),使用 useFieldArray 管理动态字段:

    tsx 复制代码
    const { 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,包括 useFormFormProvider,并展示验证和动态字段的场景。
FormProviderreact-hook-form 提供的一个组件,主要用于在嵌套的组件树中共享表单状态。它可以让子组件通过 useFormContext 访问父组件的 useForm 实例,而不需要将 useForm 的返回值传递给每一个子组件。

FormProvider 的使用步骤:

  1. 在父组件中使用 useForm 初始化表单,然后将 useForm 返回的对象通过 FormProvider 传递给子组件。
  2. 在子组件中使用 useFormContext 获取表单的方法,比如 registerhandleSubmiterrors 等。

示例代码

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} 将表单的所有方法传递给子组件,methodsuseForm 返回的对象。
  • 在表单提交时,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 中使用 registerformState 等。
  • 通过 register 绑定字段,并添加验证规则。
  • errors[name] 用于显示字段的错误信息。

总结

  1. FormProvider 用于提供表单的上下文,允许嵌套组件访问 useForm 的方法。
  2. 子组件使用 useFormContext 获取表单状态,避免逐一传递 useForm 的返回值。
  3. 这种方式尤其适合表单结构复杂或包含多个嵌套组件的情况。

评论

发表评论