使用 React Hook Form 实现动态表单。📝

2025-06-05

使用 React Hook Form 实现动态表单。📝

这次,我们将再次创建动态表单,但现在借助react-hook-form库。

注意📣:您需要具备 Typescript 知识才能学习本教程,以及 React JS 知识

你可能会对这篇文章感兴趣,我们在这篇文章中也做了同样的事情,但使用了Formik库。😉

 

目录。

📌需要用到的技术。📌 创建项目。📌

第一步。📌 创建表单对象。



📌为输入创建类型。📌现在我们借助类型来创建表单对象

📌为我们的表单创建验证模式。📌生成输入的函数。📌 创建

表单组件。📌 创建每个输入的组件。📌 使用我们的表单组件。📌 总结






📌演示。📌

代码。

 

💊 要使用的技术。

  • React JS 18.2.0
  • TypeScript 4.9.3
  • React Hook Form 7.43.0
  • Vite JS 4.1.0
  • Tailwind CSS 3.2.4(不显示安装和配置过程)。

💊 创建项目。

我们将为该项目命名:(dynamic-forms-rhf可选,您可以随意命名)。



npm create vite@latest


Enter fullscreen mode Exit fullscreen mode

我们使用 Vite JS 创建项目,并选择 React with TypeScript。

然后运行以下命令导航到刚刚创建的目录。



cd dynamic-forms-rhf


Enter fullscreen mode Exit fullscreen mode

然后我们安装依赖项。



npm install


Enter fullscreen mode Exit fullscreen mode

然后我们在代码编辑器中打开项目(在我的情况下是 VS 代码)。



code .


Enter fullscreen mode Exit fullscreen mode

💊 第一步。

在src/App.tsx文件中,我们删除所有内容并创建一个显示 的组件hello world



const App = () => {
    return (
        <div>Hello world</div>
    )
}
export default App


Enter fullscreen mode Exit fullscreen mode

🚨 注意:每次创建新文件夹时,我们都会创建一个index.ts文件,将同一文件夹内其他文件的所有函数和组件进行分组导出,这样这些函数就可以通过单一引用导入,这称为barrel 文件

让我们创建一个布局,创建一个文件夹src/components并在里面创建一个文件Layout.tsx



export const Layout = ({ children }: { children: JSX.Element | JSX.Element[] }) => {
    return (
        <>
            <h1 className='text-center my-10 text-5xl'>
                <span>Dynamic Form</span>
                <span className='font-bold bg-clip-text text-transparent  text-[#EC5990]'>
                    {' - '}
                    React Hook Form
                </span>
            </h1>

            <main className='grid sm:grid-cols-2 grid-cols-1 sm:mb-0 mb-10 gap-10 place-items-start justify-items-center px-5'>
                {children}
            </main>
        </>
    )
}


Enter fullscreen mode Exit fullscreen mode

现在,在src/App.tsx文件中,我们添加布局。



import { Layout } from './components'

const App = () => {

    return (
        <Layout>
            <span>Form</span>
        </Layout>
    )
}
export default App


Enter fullscreen mode Exit fullscreen mode

然后我们将安装必要的软件包。

  • react-hook-form,以更简单的方式处理表单。
  • 是的,处理表单验证。
  • @hookform/resolvers,将 yup 与 react-hook-form 集成。


npm install -E react-hook-form @hookform/resolvers yup


Enter fullscreen mode Exit fullscreen mode

之前我已经做过同样的动态表单练习,但使用的是 Formik 库,事实与我们要做的非常相似,唯一需要改变的是表单和输入等组件。

💊 创建表单对象。

💊 为输入创建类型。

首先,让我们创建类型。我们创建一个新文件夹src/types并创建 index.ts 文件。

现在我们首先为输入创建接口,它可以有更多的属性,但这些足以构成这个例子。

重点是InputProps接口的最后三个属性:

  • typeValue:必要的,因为我们需要告诉 Yup 输入接受什么类型的值。
  • 验证:根据输入设置为 Yup 的验证;我只设置了基本验证,但如果您查看 Yup 文档,则可以集成更多验证。
    • 如果您没有使用过 Yup,那么对您来说可能更复杂的验证可能是oneOf。此验证需要另一个输入框的引用或名称,以验证两个输入框是否包含相同的内容。此验证的一个例子是,在一个输入框中创建密码,而在另一个输入框中需要重复输入密码,并且两个值必须匹配。
  • 选项:仅当输入是选择或一组单选类型输入时,此属性才是必需的。


export interface InputProps {
    type: 'text' | 'radio' | 'email' | 'password' | 'select' | 'checkbox'
    name: string
    value: string | number | boolean
    placeholder?: string
    label?: string

    typeValue?:  'boolean' | 'number'
    validations?: Validation[]
    options?: Opt[]
}

export interface Opt {
    value: string | number
    desc: string
}

export interface Validation {
    type: 'required' | 'isEmail' | 'minLength' | 'isTrue' | 'oneOf'
    value?: string | number | boolean
    message: string
    ref?: string
}


Enter fullscreen mode Exit fullscreen mode

我们同时为即将开发的表单类型创建了此类型。
在本例中,我们只需创建两个表单。



export type FormSection = 'register' | 'another'


Enter fullscreen mode Exit fullscreen mode

💊 现在我们借助类型来创建表单对象。

感谢 Typescript,我们可以在这个对象中创建表单。
我们创建一个新文件夹src/lib,并在其中创建文件form.ts并添加以下内容:



import { FormSection, InputProps } from '../types';

export const forms: { [K in FormSection]: InputProps[] } =
{

    register: [
        {
            label: "New username",
            type: "text",
            name: "username",
            placeholder: "New username",
            value: "",
            validations: [
                {
                    type: "minLength",
                    value: 3,
                    message: "Min. 3 characters",
                },
                {
                    type: "required",
                    message: "Username is required"
                },
            ],

        },
        {
            label: "New Password",
            type: "password",
            name: "password",
            placeholder: "New password",
            value: "",
            validations: [
                {
                    type: "required",
                    message: "Password is required"
                },
                {
                    type: "minLength",
                    value: 5,
                    message: "Min. 5 characters",
                }
            ],

        },
        {
            label: 'Repeat your password',
            type: "password",
            name: "repeat_password",
            placeholder: "Repeat password",
            value: "",
            validations: [
                {
                    type: "required",
                    message: "Repeat password is required"
                },
                {
                    type: "minLength",
                    value: 5,
                    message: "Min. 5 characters",
                },
                {
                    type: 'oneOf',
                    message: 'Passwords must match',
                    ref: 'password'
                }
            ],

        },

    ],

    another: [

        {
            label: "E-mail address",
            type: "email",
            name: "email",
            placeholder: "correo@correo.com",
            value: "",
            validations: [
                {
                    type: "required",
                    message: "Email is required"
                },
                {
                    type: "isEmail",
                    message: "Email no valid"
                }
            ],

        },
        {
            type: "select",
            name: "rol",
            label: "Select an option: ",
            value: "",
            options: [
                {
                    value: "admin",
                    desc: "Admin",
                },
                {
                    value: "user",
                    desc: "User"
                },
                {
                    value: "super-admin",
                    desc: "Super Admin"
                }
            ],
            validations: [
                {
                    type: "required",
                    message: "Rol is required"
                }
            ]
        },
        {
            type: "radio",
            name: "gender",
            label: "Gender: ",
            value: "",
            options: [
                {
                    value: 'man',
                    desc: "Man"
                },
                {

                    value: "woman",
                    desc: "Woman"
                },
                {

                    value: "other",
                    desc: "Other"
                },
            ],
            validations: [
                {
                    type: "required",
                    message: "Gender is required"
                }
            ]
        },
        {
            type: "checkbox",
            name: "terms",
            typeValue: "boolean",
            label: "Terms and Conditions",
            value: false,
            validations: [
                {
                    type: "isTrue",
                    message: "Accept the terms!"
                }
            ]
        },
    ]
}


Enter fullscreen mode Exit fullscreen mode

💊 为我们的表单创建验证模式。

让我们在 src/lib 中创建一个新文件,并将其命名为getInputs.ts
我们创建一个新函数来生成每个输入的验证。
此函数接收字段,每个字段的类型均为 InputProps 。我们还将仅创建两种类型,以便 Typescript 稍后不会打扰我们。

请注意,我们创建了 YupBoolean 和 YupString 类型。如果您需要,可以添加其他类型来处理其他数据类型,例如数字或数组。例如:



  type YupNumber = Yup.NumberSchema<boolean | undefined, AnyObject, number | undefined>


我没有添加它,因为在我的界面中我没有处理任何类型数字或数组的验证。



import * as Yup from "yup";
import { AnyObject } from "yup/lib/types";
import { FormSection, InputProps } from '../types';
import { forms } from '../lib';

type YupBoolean = Yup.BooleanSchema<boolean | undefined, AnyObject, boolean | undefined>
type YupString = Yup.StringSchema<string | undefined, AnyObject, string | undefined>

const generateValidations = (field: InputProps) => {}


Enter fullscreen mode Exit fullscreen mode

首先,我们创建一个变量,并用处理输入的数据类型进行初始化。数据类型由typeValue属性获取,如果未定义,则默认数据类型为string。然后,我们执行函数:



let schema = Yup[field.typeValue || 'string']()


Enter fullscreen mode Exit fullscreen mode

然后我们将对该字段进行验证,因为它是一个数组。

在循环中,我们将使用 switch case,评估字段具有什么类型的规则。



const generateValidations = (field: InputProps) => {

    let schema = Yup[field.typeValue || 'string']()

    for (const rule of field.validations) {

        switch (rule.type) { }
    }
}


Enter fullscreen mode Exit fullscreen mode

在每次切换时,我们都会覆盖 schema 变量。方法如下:

如果它有“isTrue”验证,则表示输入处理布尔值,因此我们希望我们的模式表现为 YupBoolean,否则 Typescript 会报错。然后我们执行与每个案例相关的函数。

例如,在“isTrue”的情况下,我们执行具有完全相同名称的函数,并在其中传递消息



case 'isTrue'   : schema = (schema as YupBoolean).isTrue(rule.message);  break;


Enter fullscreen mode Exit fullscreen mode

如果验证是 oneOf,我们需要将其作为第一个参数发送一个数组,并将消息作为第二个参数发送。

如果是数组,它必须是你想要匹配的值,但在本例中,我们想要匹配另一个字段的值,因此我们使用Yup.ref ,它需要一个指向输入框name属性的字符串。
这样,验证完成后,它会检查两个字段是否包含相同的值。



case 'oneOf'    : schema = (schema as YupString)
                                            .oneOf(
                                                    [ Yup.ref(rule.ref as string) ], 
                                                    rule.message
                                                  ); 
break;


Enter fullscreen mode Exit fullscreen mode

这就是我们的第一个函数。最后,我们返回变量schema
注意,在函数的开头,我们设置了一个条件:如果字段没有验证,则返回 null 并避免执行循环。



import * as Yup from "yup";
import { AnyObject } from "yup/lib/types";
import { FormSection, InputProps } from '../types';
import { forms } from '../lib';

type YupBoolean = Yup.BooleanSchema<boolean | undefined, AnyObject, boolean | undefined>
type YupString = Yup.StringSchema<string | undefined, AnyObject, string | undefined>

const generateValidations = (field: InputProps) => {

    if (!field.validations) return null

    let schema = Yup[field.typeValue || 'string']()

    for (const rule of field.validations) {
        switch (rule.type) {
            case 'isTrue'   : schema = (schema as YupBoolean).isTrue(rule.message);  break;
            case 'isEmail'  : schema = (schema as YupString).email(rule.message);  break;
            case 'minLength': schema = (schema as YupString).min(rule?.value as number, rule.message);  break;
            case 'oneOf'    : schema = (schema as YupString).oneOf([Yup.ref((rule as any).ref)], rule.message);  break;
            default         : schema = schema.required(rule.message);  break;
        }
    }

    return schema
}


Enter fullscreen mode Exit fullscreen mode

💊 生成输入的函数。

首先,我们要创建一个函数,并将其命名为 getInputs,它是通用类型,并接收部分作为参数(也就是说,您想要获取其字段的表单,在这种情况下它可以是 signUp 或其他表单)。

我们将创建两个变量,并将它们初始化为空对象,最后必须包含新属性。




export const getInputs = <T>(section: FormSection) => {

    let initialValues: { [key: string]: any } = {};

    let validationsFields: { [key: string]: any } = {};

};


Enter fullscreen mode Exit fullscreen mode

在函数内部,我们将创建一个 for 循环。在其中,我们将遍历特定表单的字段。

  1. 在循环内部,我们将计算 initialValues 变量中的值,并使用字段的 name 属性来计算值。

  2. 我们验证该字段是否有验证。

    • 如果没有验证,则继续下一个字段。
    • 如果有验证,我们将执行在generateValidations之前创建的函数并将字段作为参数发送。
  3. 然后对于 validationsFields 变量,我们还使用字段的名称属性计算值,并分配已生成的验证模式。



for (const field of forms[section]) {

    initialValues[field.name] = field.value;

    if (!field.validations) continue;

    const schema = generateValidations(field)

    validationsFields[field.name] = schema;
}


Enter fullscreen mode Exit fullscreen mode

一旦循环完成,我们必须返回 3 个属性。

  • Yup.object中的验证模式,扩展了validationsFields属性。```tsx

验证模式:Yup.object({...validationsFields}),

- The initial values, and we will make them behave as generic so that we can use them afterwards
```tsx


initialValues: initialValues as T,


Enter fullscreen mode Exit fullscreen mode
  • 我们想要在表单中显示的字段。```tsx

输入:forms[section]

This is what our function will look like at the end
```tsx



export const getInputs = <T>(section: FormSection) => {

    let initialValues: { [key: string]: any } = {};

    let validationsFields: { [key: string]: any } = {};

    for (const field of forms[section]) {

        initialValues[field.name] = field.value;

        if (!field.validations) continue;

        const schema = generateValidations(field)

        validationsFields[field.name] = schema;
    }

    return {
        validationSchema: Yup.object({ ...validationsFields }),
        initialValues: initialValues as T,
        inputs: forms[section],
    };

};



Enter fullscreen mode Exit fullscreen mode

💊 创建表单组件。

首先,我们要为我们的 Form 组件将要接收的 props 准备接口。

  • onSubmit,执行表单的函数。
  • labelButtonSubmit,显示按钮的文本。
  • titleForm,显示表单的文本。

最后 3 个属性返回我们用来生成输入及其验证的函数。



interface Props {
    onSubmit: (data: unknown) => void
    labelButtonSubmit?: string
    titleForm?: string

    initialValues: unknown
    validationSchema: SchemaForm
    inputs: InputProps[]
}


Enter fullscreen mode Exit fullscreen mode

validationSchema 属性属于SchemaForm类型。



// src/types/index.ts
export type SchemaForm = OptionalObjectSchema<{
    [x: string]: any;
}, AnyObject, TypeOfShape<{
    [x: string]: any;
}>>


Enter fullscreen mode Exit fullscreen mode

现在我们创建组件,并在其中解构组件接收的 props。

然后我们使用 useForm 的钩子,我们将建立一个对象作为参数,我们访问该属性:

  • resolver,设置验证方案,为此我们使用函数yupResolver,并将props 提供的validationSchema作为参数传递。
  • defaultValues ,用于建立默认值,我们将分配initialValues的 props

请注意,我们不会解构useForm钩子的任何内容。



import { yupResolver } from '@hookform/resolvers/yup'
import { useForm } from 'react-hook-form'

export const Form = ({ ...props }: Props) => {
    const {
        initialValues,
        inputs,
        onSubmit,
        validationSchema,
        titleForm,
        labelButtonSubmit = 'Submit'
    } = props

    const formMethods = useForm({
        resolver: yupResolver(validationSchema),
        defaultValues: { ...(initialValues as any) }
    })

    return (
        <></>
    )
}


Enter fullscreen mode Exit fullscreen mode

接下来,我们将使用一个为我们提供 react-hook-form 的组件,它是FormProvider,我们将传播useForm钩子的formMethods

FormProvider将帮助我们与嵌套在 FormProvider 内部的组件(输入框)沟通表单的状态。这样做的目的是分离组件,避免将所有内容放在同一个文件中

在 FormProvider 内部,我们将放置一个表单,并在表单标签的 onSubmit 方法中执行 formMethods 的一个属性,即handleSubmit,并将通过 props 接收组件 Form 的 onSubmit 作为参数传递。

此handleSubmit仅当每个输入都没有错误时才会执行,并且执行时它将返回每个输入的值。



import { FormProvider, useForm } from 'react-hook-form'

// interface

export const Form = ({ ...props }: Props) => {
    // props

    const formMethods = useForm({
        resolver: yupResolver(validationSchema),
        defaultValues: { ...(initialValues as any) }
    })

    return (
        <FormProvider {...formMethods}>
            <form
                onSubmit={formMethods.handleSubmit(onSubmit)}
                className='bg-secondary rounded-md p-10 pt-5 shadow-2xl shadow-primary/30 flex flex-col gap-2 border border-primary w-full min-h-[390px]'
            >
                <section className='flex-1 flex flex-col gap-3'>
                    {/* inputs here */}
                </section>
            </form>
        </FormProvider>
    )
}


Enter fullscreen mode Exit fullscreen mode

现在我们要创建一个函数,用于返回不同类型的输入。
我们使用从 Form 组件接收的 props 中解构出来的input 属性。

根据输入的类型,我们将呈现一个或另一个输入。

请注意,我们正在使用尚未创建的组件。另请注意,我们将从每个输入的属性中排除验证 typeValue 和 value,因为它们是我们的输入不需要直接使用的值。

关于这个函数,有一点需要改进,那就是你可以创建一个单独的组件,并创建一个包含组件和输入类型的字典。
在本例中,我没有这样做,以免进一步扩展。



const createInputs = () =>
    inputs.map(({ validations, typeValue, value, ...inputProps }) => {

        switch (inputProps.type) {
            case 'select':
                return <CustomSelect {...inputProps} key={inputProps.name} />
            case 'checkbox':
                return <CustomCheckbox {...inputProps} key={inputProps.name} />
            case 'radio':
                return <CustomRadio {...inputProps} key={inputProps.name} />
            default:
                return <CustomInput {...inputProps} key={inputProps.name} />
        }
    })



Enter fullscreen mode Exit fullscreen mode

最后,我们在 section 标签内执行createInputs函数。然后我们就可以立即创建自定义输入了。



// imports 

// interface
export const Form = ({ ...props }: Props) => {
    // props

    const formMethods = useForm({
        resolver: yupResolver(validationSchema),
        defaultValues: { ...(initialValues as any) }
    })

    const createInputs = () =>
        inputs.map(({ validations, typeValue, value, ...inputProps }) => {
            switch (inputProps.type) {
                case 'select':
                    return <CustomSelect {...inputProps} key={inputProps.name} />
                case 'checkbox':
                    return <CustomCheckbox {...inputProps} key={inputProps.name} />
                case 'radio':
                    return <CustomRadio {...inputProps} key={inputProps.name} />
                default:
                    return <CustomInput {...inputProps} key={inputProps.name} />
            }
        })

    return (
        <FormProvider {...formMethods}>
            <form
                onSubmit={formMethods.handleSubmit(onSubmit)}
                className='bg-secondary rounded-md p-10 pt-5 shadow-2xl shadow-primary/30 flex flex-col gap-2 border border-primary w-full min-h-[390px]'
            >
                <section className='flex-1 flex flex-col gap-3'>
                    { createInputs() }
                </section>

            </form>
        </FormProvider>
    )
}


Enter fullscreen mode Exit fullscreen mode

💊 创建每个输入的组件。

首先,我们将创建一个错误消息,该消息将在每次输入验证失败时显示。

在src/components我们创建ErrorMessage.tsx



interface Props { error?: string }

export const ErrorMessage = ({ error }: Props) => {
    if (!error) return null

    return (
        <div className='w-full grid place-content-end'>
            <p className='text-red-400 text-sm'>{error}</p>
        </div>
    )
}


Enter fullscreen mode Exit fullscreen mode

现在,我们将创建一个新文件夹src/components/inputs,并在其中创建 4 个文件。

我们将要创建的这四个组件接收CustomInputProps类型的 props 。你可以将其放在 src/types/index.ts 文件中。



export type CustomInputProps = Omit<InputProps, 'validations' | 'typeValue' | 'value'>


而且由于我们创建的每个输入都位于FormProvider中,我们可以使用 react-hook-form 的另一个自定义钩子,即useFormContext,这个钩子将帮助我们将表单的状态与输入连接起来。

  1. 自定义通用输入.tsx

useFormContext中,我们获取了 register 属性,以及 formState 中的 error 属性。



const {
        register,
        formState: { errors }
    } = useFormContext()


Enter fullscreen mode Exit fullscreen mode

我们创建错误,使用组件接收的 prop 名称计算错误对象并获取消息。



const error = errors[name]?.message as string | undefined


Enter fullscreen mode Exit fullscreen mode

在构造输入时,我们需要扩展register函数的属性,并传入 prop名称,以便 react-hook-form 识别此输入应包含的错误和验证。然后,如果输入包含其他属性(例如placeholder
) ,则需要扩展其他属性



<input
    className='py-1 px-2 rounded w-full text-black'
    {...register(name)}
    {...props}
    id={id}
/>


Enter fullscreen mode Exit fullscreen mode

这个组件最终将会是这个样子。



import { useFormContext } from 'react-hook-form'
import { ErrorMessage } from '../../components'
import { CustomInputProps } from '../../types'

export const CustomInput = ({ name, label, ...props }: CustomInputProps) => {
    const {
        register,
        formState: { errors }
    } = useFormContext()

    const error = errors[name]?.message as string | undefined

    const id = `${name}-${props.type}-${label}`

    return (
        <div className='w-full flex gap-1 flex-col'>
            {label && (
                <label className='text-white text-sm' htmlFor={id}>
                    {label}
                </label>
            )}

            <input
                className='py-1 px-2 rounded w-full text-black'
                {...register(name)}
                {...props}
                id={id}
            />

            <ErrorMessage error={error} />
        </div>
    )
}


Enter fullscreen mode Exit fullscreen mode
  1. 自定义复选框.tsx


import { useFormContext } from 'react-hook-form'
import { ErrorMessage } from '../../components'
import { CustomInputProps } from '../../types'

export const CustomCheckbox = ({ name, label, ...props }: CustomInputProps) => {
    const {
        register,
        formState: { errors }
    } = useFormContext()

    const error = errors[name]?.message as string | undefined

    return (
        <div>
            <label className='flex gap-2 items-center cursor-pointer w-fit'>
                <input {...props} {...register(name)} />
                {label}
            </label>

            <ErrorMessage error={error} />
        </div>
    )
}


Enter fullscreen mode Exit fullscreen mode
  1. 自定义选择.tsx

此输入几乎与所有其他输入相同,只有在这里我们有 prop 选项,其中可以选择的选择的值将会出现。



import { useFormContext } from 'react-hook-form'
import { ErrorMessage } from '../../components'
import { CustomInputProps } from '../../types'

export const CustomSelect = ({ name, label, options, ...props }: CustomInputProps) => {
    const {
        register,
        formState: { errors }
    } = useFormContext()

    const error = errors[name]?.message as string | undefined
    const id = `${name}-${props.type}-${label}`

    return (
        <div className='flex flex-col gap-2'>
            <div className='flex items-center gap-4'>
                <label htmlFor={id}>{label}</label>

                <select {...register(name)} {...props} id={id} className='p-2 rounded flex-1 text-black'>

                    <option value=''>--- Select option ---</option>

                    {options &&
                        options.map(({ desc, value }) => (
                            <option key={value} value={value}>
                                {desc}
                            </option>
                    ))}

                </select>

            </div>
            <ErrorMessage error={error} />
        </div>
    )
}


Enter fullscreen mode Exit fullscreen mode
  1. 自定义RadioGroup

与 CustomSelect.tsx 非常相似。只是这里我们渲染了一个 radio 类型的输入。



import { useFormContext } from 'react-hook-form'
import { ErrorMessage } from '../../components'
import { CustomInputProps } from '../../types'

export const CustomRadio = ({ name, label, options, ...props }: CustomInputProps) => {
    const {
        register,
        formState: { errors }
    } = useFormContext()

    const error = errors[name]?.message as string | undefined

    return (
        <div className='flex flex-col'>
            <div className='flex items-center gap-4'>
                <label>{label}</label>

                <section className='flex justify-between flex-1'>
                    {options &&
                        options.map(({ desc, value }) => (

                            <label
                                key={value}
                                className='flex items-center gap-1 cursor-pointer hover:underline rounded p-1'
                            >
                                <input {...register(name)} {...props} value={value} type='radio' />
                                {desc}
                            </label>

                        ))}
                </section>
            </div>
            <ErrorMessage error={error} />
        </div>
    )
}


Enter fullscreen mode Exit fullscreen mode

💊 使用我们的表单组件。

现在我们转到src/App.tsx文件。

使用表单组件。

我们必须执行getInputs函数并获取验证、初始值和输入。我们将在组件外部执行此操作。我们还创建一个接口,以便初始值的行为与该接口类似。



interface SignUpFormType {
    username: string
    password: string
    repeat_password: string
}

const signUpForm = getInputs<SignUpFormType>('register')


Enter fullscreen mode Exit fullscreen mode

然后我们导入 Form 组件,并传播getInput返回的属性。同时,我们也传递了其他 props。



import { Layout, Form } from './components'
import { getInputs } from './lib'

interface SignUpFormType {
    username: string
    password: string
    repeat_password: string
}


const signUpForm = getInputs<SignUpFormType>('register')

const App = () => {

    const onSubmitSignUp = (data: unknown) => console.log({ singUp: data })

    return (
        <Layout>
            <Form
                {...signUpForm}
                onSubmit={onSubmitSignUp}
                titleForm='Sign Up!'
                labelButtonSubmit='Create account'
            />
        </Layout>
    )
}
export default App


Enter fullscreen mode Exit fullscreen mode

如果您想覆盖初始值,只需通过扩展初始值并覆盖所需的值来创建一个新的常量。然后将新值传递给initialValues属性。




const App = () => {
    const onSubmitSignUp = (data: unknown) => console.log({ singUp: data })

    const initialValuesSignUp: SignUpFormType = {
        ...signUpForm.initialValues,
        username: '@franklin361'
    }

    return (
        <Layout>
            <Form
                {...signUpForm}

                initialValues={initialValuesSignUp}

                onSubmit={onSubmitSignUp}
                titleForm='Sign Up!'
                labelButtonSubmit='Create account'
            />
        </Layout>
    )
}
export default App


Enter fullscreen mode Exit fullscreen mode

您还可以动态地包含多个表单。



import { Layout, Form } from './components'
import { getInputs } from './lib'

interface SignUpFormType {
    username: string
    password: string
    repeat_password: string
}

interface AnotherFormType {}

const signUpForm = getInputs<SignUpFormType>('register')
const anotherForm = getInputs<AnotherFormType>('another')

const App = () => {
    const onSubmitSignUp = (data: unknown) => console.log({ singUp: data })

    const onSubmitAnotherForm = (data: unknown) => console.log({ another: data })

    const initialValuesSignUp: SignUpFormType = {
        ...signUpForm.initialValues,
        username: '@franklin361'
    }

    return (
        <Layout>
            <Form
                {...signUpForm}
                initialValues={initialValuesSignUp}
                titleForm='Sign Up!'
                onSubmit={onSubmitSignUp}
                labelButtonSubmit='Create account'
            />

            <Form
                {...anotherForm}
                titleForm='Another form!'
                onSubmit={onSubmitAnotherForm}
                labelButtonSubmit='Send info'
            />
        </Layout>
    )
}
export default App


Enter fullscreen mode Exit fullscreen mode

覆盖

💊 结论。

React Hook Form 是我最喜欢的库之一,因为它比其他流行的库(例如 Formik)具有某些优势;例如,捆绑包大小更小、依赖项更少、重新渲染次数更少等。😉。

但这两个仍然是非常常用的库。

我希望你喜欢这篇文章,也希望我能帮助你了解如何使用 React Hook Form 制作动态表单🙌。

如果您知道任何其他不同或更好的方法来制作此应用程序,请随时发表评论。

如果您有兴趣就某个项目与我联系,我邀请您查看我的投资组合。!富兰克林·马丁内斯·卢卡斯

🔵 别忘了在推特上关注我:@Frankomtz361

💊 演示。

https://dynamic-form-rhf.netlify.app/

💊 源代码。

https://github.com/Franklin361/dynamic-form-rhf/

文章来源:https://dev.to/franklin030601/dynamic-forms-with-react-hook-form-2ml8
PREV
使用 React JS 实现图像延迟加载
NEXT
React JS 中的代码拆分