为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
相关产品推荐
相关产品推荐

