You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为React Hook Form v7自定义组件添加onChange及实现通用化

解决React Hook Form密码输入组件的复用与onChange问题

一、让组件支持通用表单值类型

原组件硬绑定了特定的FormValues,导致只能在注册页复用。通过泛型改造,让组件适配任意表单值结构:

import { UseFormRegister } from "react-hook-form";
import { useState } from "react";
import { BsEye, BsEyeSlash } from "react-icons/bs";
// 替换为你的实际样式组件
import styled from "styled-components";

const Styled = {
  Container: styled.div`position: relative;`,
  Eye: styled.button`position: absolute; right: 8px; top: 50%; transform: translateY(-50%); border: none; background: transparent; cursor: pointer;`
};

const AuthStyled = {
  Input: styled.input`padding: 8px 12px; width: 100%; box-sizing: border-box;`
};

// 泛型Props,支持任意表单值类型
type PasswordInputProps<T> = {
  name: keyof T;
  value: string;
  register: UseFormRegister<T>;
  onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
};

// 泛型组件定义
export default function PasswordInput<T extends Record<string, any>>({
  name,
  value,
  register,
  onChange
}: PasswordInputProps<T>) {
  const [passwordShown, setPasswordShown] = useState(false);

  const togglePassword = () => {
    setPasswordShown(!passwordShown);
  };

  // 获取react-hook-form的默认注册属性
  const registerProps = register(name);

  return (
    <Styled.Container>
      <AuthStyled.Input
        type={passwordShown ? "text" : "password"}
        value={value}
        {...registerProps}
        // 合并默认onChange与自定义onChange
        onChange={(e) => {
          registerProps.onChange?.(e);
          onChange?.(e);
        }}
      />
      <Styled.Eye onClick={togglePassword}>
        {!passwordShown ? <BsEye /> : <BsEyeSlash />}
      </Styled.Eye>
    </Styled.Container>
  );
}

二、添加自定义onChange支持

通过拆分register返回的属性,先执行react-hook-form默认的状态同步逻辑,再执行自定义的onChange回调,既不破坏表单状态管理,又能添加额外业务逻辑。

使用示例

注册页使用(原表单结构)

import { useForm } from "react-hook-form";
import PasswordInput from "./PasswordInput";

type RegisterFormValues = {
  email: string;
  fullName: string;
  password: string;
  confirmPassword: string;
};

export default function RegisterPage() {
  const { register, watch } = useForm<RegisterFormValues>();
  const confirmPassword = watch("confirmPassword");

  return (
    <div>
      <PasswordInput
        name="password"
        value={watch("password")}
        register={register}
        onChange={(e) => {
          // 自定义逻辑:比如密码强度检测
          console.log("密码变更:", e.target.value);
        }}
      />
      <PasswordInput
        name="confirmPassword"
        value={confirmPassword}
        register={register}
      />
    </div>
  );
}

登录页使用(新表单结构)

import { useForm } from "react-hook-form";
import PasswordInput from "./PasswordInput";

type LoginFormValues = {
  username: string;
  password: string;
};

export default function LoginPage() {
  const { register, watch } = useForm<LoginFormValues>();

  return (
    <div>
      <PasswordInput
        name="password"
        value={watch("password")}
        register={register}
        onChange={(e) => {
          console.log("登录密码变更:", e.target.value);
        }}
      />
    </div>
  );
}

关键修改说明

  • 泛型改造:通过T extends Record<string, any>让组件适配任意表单值类型,摆脱对特定FormValues的依赖。
  • onChange合并:保留react-hook-form的默认状态同步逻辑,同时支持自定义回调。
  • 类型安全:通过keyof T约束name参数,保证表单字段名的合法性。

内容的提问来源于stack exchange,提问作者AfonsoSalgadoSousa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 21:30:26