发布于 2026-01-05 9 阅读
0

使用 Formik 和 React JS 构建动态表单。📝 使用 Formik 和 React JS 构建动态表单。📝

使用 Formik 和 React JS 构建动态表单。📝

使用 Formik 和 React JS 构建动态表单。📝

这次我将向大家展示如何使用 React JS 和 TypeScript,通过 Formik 和 Yup 创建动态表单。

任何形式的反馈都非常欢迎,谢谢!希望您喜欢这篇文章。🤗

⚠️ 注意:您需要具备 React JS、Hooks 和 TypeScript 的基础知识。

 

目录

📌将要使用的技术。📌 创建

项目。📌初步步骤。📌

设计表单。📌表单中集成 Formik。📌设计动态表单





📌创建输入组件。📌

创建复选框组件。📌

创建单选按钮组组件。📌

创建下拉选择组件。

📌创建表单对象。📌

使用 函数生成验证规则。

📌初始化表单值。📌 使用getInputs 函数。📌

结论。📌代码。



 

🖊️ 将要使用的技术。

  • ▶️ React JS (v 18)
  • ▶️ Vite JS
  • ▶️ TypeScript
  • ▶️ 福米克
  • ▶️ 原生 CSS(您可以在本文末尾的仓库中找到样式)

🖊️ 创建项目。

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



npm init vite@latest


Enter fullscreen mode Exit fullscreen mode

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

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



cd formik-dynamic


Enter fullscreen mode Exit fullscreen mode

然后我们安装依赖项。



npm install


Enter fullscreen mode Exit fullscreen mode

然后我们在代码编辑器(我使用的是 VS Code)中打开项目。



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文件,用于对同一文件夹中其他文件的所有函数和组件进行分组和导出,以便这些函数可以通过单个引用导入,这被称为桶形文件

创建src/components文件夹,并在其中创建Layout.tsx文件,添加以下内容:



interface ILayout {
    children: JSX.Element | JSX.Element[]
    title: string
}

export const Layout = ({ children, title }: ILayout) => {
    return (
        <div className="container">
            <h2 className="title">{title}</h2>
            {children}
        </div>
    )
}


Enter fullscreen mode Exit fullscreen mode

接下来我们将创建src/pages文件夹。目的是模拟不同的页面,因为我们将解释 Formik 的基本用法,然后另一个页面将解释动态表单。

我们将创建一个FormikBasic.tsx文件,暂时添加以下内容:



import { Layout } from "../components"

export const FormikBasic = () => {
    return (
        <Layout title="Formik Basic">
            <div>Hello world</div>
        </Layout>
    )
}


Enter fullscreen mode Exit fullscreen mode

然后我们将其导入到文件中src/App.tsx



import { FormikBasic } from "./pages"

const App = () => {
  return (
    <>
      <FormikBasic />
    </>
  )
}
export default App


Enter fullscreen mode Exit fullscreen mode

🖊️ 设计表格。

在内部,src/components/FormikBasic.tsx我们将创建一个基本表单,其中使用了一些表单中最常用的控件。



import { Layout } from "../components"

export const FormikBasic = () => {

    return (
        <Layout title="Formik Basic">
            <form>

                <input type="text" placeholder="Full name"/>

                <input type="email" placeholder="E-mail"/>

                <input type="password" placeholder="Password"/>

                <div>
                    <label htmlFor="rol">Select an option:</label>
                    <select id="rol" >
                        <option value="">--- Select ---</option>
                        <option value="admin">Admin</option>
                        <option value="user">User</option>
                        <option value="super">Super Admin</option>
                    </select>
                </div>

                <div className='radio-group'>
                    <b>Gender: </b>
                    <label ><input type="radio"/> Man</label>
                    <label ><input type="radio"/> Woman</label>
                    <label ><input type="radio"/> Other</label>
                </div>

                <label>
                    <input type="checkbox" {...getFieldProps('terms')} />
                    Terms and Conditions
                </label>

                <button type="submit">Submit</button>
            </form>
        </Layout>
    )
}


Enter fullscreen mode Exit fullscreen mode

加上样式后,应该看起来像这样👀:

基本形式

🖊️ 在我们的表单中实施 Formik。

接下来,我们将安装Formik来管理我们的表单,另一个非常有用的软件包是Yup,用于管理我们表单的验证。



npm install formik yup


Enter fullscreen mode Exit fullscreen mode

通常情况下,要使用 Formik 并管理我们的表单,我们会通过useFormik这个钩子来实现

我们使用 Formik 的钩子函数,并将传递一个包含 3 个属性的对象(它还有更多属性,但我们只会用到这 3 个)。

  • initialValues是一个包含表单初始值的对象,它引用每个输入框或字段。
  • validationSchema,这里我们基本上会使用 Yup 来建立各个表单字段的验证。
  • onSubmit是一个接收表单值并仅在所有验证都通过后才执行的函数。


useFormik({
    initialValues:,
    validationSchema: ,
    onSubmit:
});


Enter fullscreen mode Exit fullscreen mode

initialValues将包含一个定义表单中每个字段的对象,并将用空字符串初始化,terms字段将是一个布尔值,初始化为 false。

validationSchema将包含一个 Yup.object(别忘了在文件顶部导入 Yup import * as Yup from 'yup'),它是一个接收定义验证规则的对象作为参数的函数。(注意,每个属性的键必须与initialValues的属性匹配)。每个属性内部都将包含其验证规则。

onSubmit实际上只会将值显示在控制台中。



const { handleSubmit, errors, touched, getFieldProps } = useFormik({
    initialValues: {
        fullName: '',
        email: '',
        password: '',
        rol: '',
        gender: '',
        terms: false
    },
    validationSchema: Yup.object({
        fullName: Yup.string().min(3, 'Min. 3 characters').required('Required'),
        email: Yup.string().email('It should be a valid email').required('Required'),
        password: Yup.string().min(6, 'Min. 6 characters').required('Required'),
        terms: Yup.boolean().isTrue('You must accept the terms!'),
        rol: Yup.string().required('Required'),
        gender: Yup.string().required('Required'),
    }),
    onSubmit: values => {
        console.log(values)
    }
});


Enter fullscreen mode Exit fullscreen mode

该钩子会返回多个属性和函数,但我们只需要:

  • `handleSubmit`函数是您必须传递给表单以提交表单的函数。此函数必须在表单标签的 `onSubmit` 事件中执行。

  • errors是一个对象,其中包含每个字段的错误,并以您在initialValues中放置的属性名称进行标识

  • `touted` 属性指示输入框是否已被点击。这将用于在用户点击输入框后执行该字段的验证并显示错误,而不是在应用程序启动时用户刚看到表单时就显示错误。此属性是一个对象,其属性名称与您在`initialValues`中设置的属性名称相同。

  • `getFieldProps`是一个获取器函数,它提供输入框正常工作所需的各种属性(`name`、`value`、`onChange`、`onBlur`)。通常我们也可以使用 `useFormik` 获取这些属性,但那样会增加代码量,因为需要为每个属性单独编写代码(当我们需要对某个属性进行特定操作时,这种方法很有用,但在此例中并不需要)。它接收一个 `name` 参数,该参数必须与 ` initialValues`中的某个属性匹配。



const { handleSubmit, errors, touched, getFieldProps } = useFormik({
    // ...props
});


Enter fullscreen mode Exit fullscreen mode

现在我们的钩子已经准备好了,我们将修改我们的 JSX。

首先在表单标签中放置 handleSubmit 和 noValidate。



<form noValidate onSubmit={handleSubmit}>


Enter fullscreen mode Exit fullscreen mode

现在,在文本、电子邮件和密码输入框中,我们输入以下内容。

我们将 getFieldProps 返回的属性展开(它接收一个名称作为参数,该名称必须具有initialValues的某些属性)。

我们通过 className 进行验证,如果输入框被点击且存在相应的错误,则会添加类名“error_input”(尽管我实际上从未在样式中使用该类)。



<input
    // ...attr
    {...getFieldProps('password')}
    className={`${(touched.password && errors.password) && 'error_input'}`}
/>


Enter fullscreen mode Exit fullscreen mode

className 属性中的这种条件可用于显示错误消息:



{(touched.password && errors.password) && <span className="error">{errors.password}</span>}


Enter fullscreen mode Exit fullscreen mode

对于下拉选择框和复选框类型的输入框,我们只需添加 getFieldProps 并展开此函数返回的值即可。



<select id="rol" {...getFieldProps('rol')} >
    // ...options
</select>

<input type="checkbox" {...getFieldProps('terms')} />


Enter fullscreen mode Exit fullscreen mode

对于单选按钮类型的输入框,
我们添加 getFieldProps 函数,并将该函数返回的值展开。

我们将创造价值。

在 checked 属性中,我们将评估 getFieldProps 的 value 属性是否等于我们输入的值,如果等于,则该输入单选按钮必须处于激活状态。



<input type="radio"
    {...getFieldProps('gender')}
    value='women'
    checked={getFieldProps('gender').value === 'women'}
/>


Enter fullscreen mode Exit fullscreen mode

我们的整个组件看起来会像这样👀:



import * as Yup from 'yup';
import { useFormik } from "formik";
import { Layout } from "../components"

export const FormikBasic = () => {

    const { handleSubmit, errors, touched, getFieldProps } = useFormik({
        initialValues: {
            fullName: '',
            email: '',
            password: '',
            rol: '',
            gender: '',
            terms: false
        },
        validationSchema: Yup.object({
            fullName: Yup.string().min(3, 'Min. 3 characters').required('Required'),
            email: Yup.string().email('It should be a valid email').required('Required'),
            password: Yup.string().min(6, 'Min. 6 characters').required('Required'),
            terms: Yup.boolean().isTrue('You must accept the terms!'),
            rol: Yup.string().required('Required'),
            gender: Yup.string().required('Required'),
        }),
        onSubmit: values => {
            // TODO: some action
        }
    });

    return (
        <Layout title="Formik Basic">
            <form noValidate onSubmit={handleSubmit}>

                <input
                    type="text"
                    placeholder="Full name"
                    {...getFieldProps('fullName')}
                    className={`${(touched.fullName && errors.fullName) && 'error_input'}`}
                />
                {(touched.fullName && errors.fullName) && <span className="error">{errors.fullName}</span>}
                <input
                    type="email"
                    placeholder="E-mail"
                    {...getFieldProps('email')}
                    className={`${(touched.email && errors.email) && 'error_input'}`}
                />
                {(touched.email && errors.email) && <span className="error">{errors.email}</span>}
                <input
                    type="password"
                    placeholder="Password"
                    {...getFieldProps('password')}
                    className={`${(touched.password && errors.password) && 'error_input'}`}
                />
                {(touched.password && errors.password) && <span className="error">{errors.password}</span>}
                <div>
                    <label htmlFor="rol">Select an option:</label>

                    <select id="rol" {...getFieldProps('rol')} >
                        <option value="">--- Select ---</option>
                        <option value="admin">Admin</option>
                        <option value="user">User</option>
                        <option value="super">Super Admin</option>
                    </select>
                </div>


                <div className='radio-group'>
                    <b>Gender: </b>
                    <label >
                        <input type="radio"
                            {...getFieldProps('gender')}
                            checked={getFieldProps('gender').value === 'man'}
                            value='man'
                        />
                        Man
                    </label>
                    <label >
                        <input type="radio"
                            {...getFieldProps('gender')}
                            checked={getFieldProps('gender').value === 'women'}
                            value='women'
                        />
                        Woman
                    </label>
                    <label >
                        <input type="radio"
                            {...getFieldProps('gender')}
                            checked={getFieldProps('gender').value === 'other'}
                            value='other'
                        />
                        Other
                    </label>

                    {(touched.gender && errors.gender) && <span className="error">{errors.gender}</span>}
                </div>
                {(touched.rol && errors.rol) && <span className="error">{errors.rol}</span>}
                <label>
                    <input type="checkbox" {...getFieldProps('terms')} />
                    Terms and Conditions
                    {(touched.terms && errors.terms) && <span className="error">{errors.terms}</span>}
                </label>

                <button type="submit">Submit</button>
            </form>
        </Layout>
    )
}


Enter fullscreen mode Exit fullscreen mode

目前我们已经实现了表单及其验证功能的基本功能。

🖊️ 设计我们的动态表单。

现在我们要创建一个新页面,在页面中src/pages创建FormikDynamic.tsx 文件
目前我们先添加以下内容:



import { Layout } from "../components"

export const FormikDynamic = () => {
    return (
        <Layout title="Formik Dynamic">
            <div>Hello world</div>
        </Layout>
    )
}


Enter fullscreen mode Exit fullscreen mode

该组件如图所示src/App.tsx



import { FormikBasic, FormikDynamic } from "./pages"

const App = () => {
  return (
    <>
      <FormikDynamic />
      {/* <FormikBasic /> */}
    </>
  )
}
export default App


Enter fullscreen mode Exit fullscreen mode

在内部,src/components/FormikDynamic.tsx我们将添加 Formik 提供的用于管理表单的组件。

我们导入 Formik 组件,它的 props 与useFormik hook类似,同时我们将使用上面提到的三个属性。然后我们将设置 initialValues 和 validationSchema。



import { Formik } from "formik"

export const FormikDynamic = () => {
    return (
        <Layout title="Formik Dynamic">

            <Formik
                initialValues={{}}
                validationSchema={{}}
                onSubmit={ values => console.log(values) }
            > 

            </Formik>
        </Layout>
    )
}


Enter fullscreen mode Exit fullscreen mode

Formik 组件使用“渲染属性”模式,为此它会接收组件内部的一个函数。该函数也会返回一些属性,就像 useFormik hook 一样,但在这里我们不会使用这些属性。

该函数将渲染 Formik 的另一个组件,即 Form,因为它类似于 form 标签,但已经具有 handleSubmit 方法。



import { Formik } from "formik"

export const FormikDynamic = () => {
    return (
        <Layout title="Formik Dynamic">

            <Formik
                initialValues={{}}
                validationSchema={{}}
                onSubmit={ values => console.log(values) }
            > 
            {
                () => (
                    <Form noValidate>

                        <button className="btn btn_submit" type="submit">Submit</button>
                    </Form>
                )
            }         
            </Formik>
        </Layout>
    )
}


Enter fullscreen mode Exit fullscreen mode

现在我们需要输入数据,但在这个例子中,我们会将它们拆分成可重用的组件。

✏️ 创建输入组件。

在文件夹内src/components创建文件CustomTextInput.tsx

在文件中,我们创建一个组件,并定义将传递给该组件的 props 接口。

名称是识别输入内容最重要的部分之一。

最后我们加上了,[x: string]: any因为如果您需要添加其他属性,则不必在接口中建立它,从而避免接口增长(这是可选的,如果您想定义每个属性,也可以这样做)。

例如,在界面中我们没有定义autoComplete属性,但是当我们使用该组件时,如果我们将 autoComplete 属性及其值(“off”/“on”)作为属性放入组件中,则不会报错。

我们添加了一个没有属性的简单输入框。



interface Props {
    name: string;
    type: string;
    placeholder?: string;
    [x: string]: any
}

export const CustomTextInput = (props: Props) => {
    return ( <input /> )
}


Enter fullscreen mode Exit fullscreen mode

现在,我们将使用Formik 提供的useField钩子。这个钩子是连接输入和 Formik 的关键。

useField钩子函数可以接受对象或字符串作为参数,但必须始终传递输入框的名称。在这种情况下,传递整个prop对象或仅传递 prop.name都没有问题

该钩子函数返回一个包含三个位置的数组。

  • FieldProps包含了输入框在 onChange、value、onBlur 等状态下工作所需的一切属性。

  • FieldMetaProps包含对字段计算的值,这些值可用于设置字段样式或更改字段,例如 touched 和 errors。

  • FieldHelperProps包含一些辅助函数,允许以命令方式更改字段的值。例如 setValue。

我们感兴趣的是 FieldProps 的第一个位置的值,我们将到达组件的 props 和给我们提供钩子的字段值都传播出去。



import { useField } from "formik"

interface Props {
    name: string;
    type: string;
    placeholder?: string;
    [x: string]: any
}

export const CustomTextInput = (props: Props) => {

    const [field] = useField(props)

    return (
        <input {...field} {...props} />
    )
}


Enter fullscreen mode Exit fullscreen mode

您也可以使用第二个位置(FieldMetaProps)来显示错误,这与 Formik 基本版中的操作完全相同。但我们最好使用 Formik 提供的ErrorMessage组件来显示错误。

ErrorMessage必须接收输入框的名称,默认情况下它不仅显示文本,而且不显示 HTML 标签,因此我们放置组件属性来告诉它渲染一个 span 标签



import { ErrorMessage, useField } from "formik"

interface Props {
    name: string;
    type: string;
    placeholder?: string;
    [x: string]: any
}

export const CustomTextInput = (props: Props) => {

    const [field] = useField(props)

    return (
        <>
            <input {...field} {...props} />
            <ErrorMessage name={props.name} component="span" className="error" />
        </>
    )
}


Enter fullscreen mode Exit fullscreen mode

以上就是我们的意见。

✏️ 创建复选框组件。

将此输入组件创建为复选框类型与创建普通输入组件基本相同。唯一不同的是 JSX 结构和接口。



import { ErrorMessage, useField } from "formik"

interface Props {
    label: string;
    name: string;
    [x: string]: any
}


export const CustomCheckBox = (props: Props) => {
    const [field] = useField(props)

    return (
        <label className="label_check">
            <input type="checkbox" {...field} {...props} />
            <span>{props.label}</span>
            <ErrorMessage name={props.name} component="span" className="error" />
        </label>
    )
}


Enter fullscreen mode Exit fullscreen mode

✏️ 创建 RadioGroup 组件。

该组件由一组无线电类型的输入组成。

它与前面的组件几乎相同,只是这里我们有一个选项数组,这些选项是输入的值和描述。

我们需要逐个检查这些选项,并将它的 value 属性设置为输入框的值,还要设置它的 checked 属性,该属性会有一个条件:如果字段的值等于输入框的值,则该输入框将被激活。

设置该值是必要的,因为此输入框的值不会改变,始终保持不变,改变的是 checked 属性的值。

我们还设置了值来标识输入,因为输入是单选按钮,所以您只能选择一个组中的一个,它们必须具有相同的属性名称。

因此,当输入框发生 onChange 事件时,Formik 会获取其 value 属性并进行设置。然后,它会判断设置的值是否等于输入框组中的某个值,如果是,则将其 checked 属性设置为“选中”。



import { useField, ErrorMessage } from 'formik';

type Opt = { value: string | number, desc: string }

interface Props {
    options: Opt[]
    name: string
    label: string
    [x: string]: any
}

export const CustomRadioGroup = ({ label, options, ...props }: Props) => {
    const [field] = useField(props)

    return (
        <div className='radio-group'>
            <b>{label}</b>
            {
                options.map(opt => (
                    <label key={opt.value}>
                        <input
                            {...field}
                            {...props}
                            type="radio"
                            value={opt.value}
                            checked={opt.value === field.value}
                        />
                        {opt.desc}
                    </label>
                ))
            }
            <ErrorMessage name={props.name} component="span" className="error" />
        </div>
    )
}


Enter fullscreen mode Exit fullscreen mode

✏️ 创建 Select 组件。

实现这个下拉列表组件几乎与单选按钮组相同。唯一的区别在于 JSX 的结构,select 标签用于放置字段属性以及传递给组件的属性。

在选择框中,我们会遍历选项,并确定其值和描述。



import { ErrorMessage, useField } from "formik"

interface Props {
    options: Opt[]
    label: string;
    name: string;
    [x: string]: any
}

type Opt = { value: string | number, desc: string }

export const CustomSelect = ({ label,options, ...props }: Props) => {
    const [field] = useField(props)

    return (
        <>
            <div>
                <label htmlFor={props.name || props.id}> {label} </label>

                <select {...field} {...props} >

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

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

                </select>
            </div>
            <ErrorMessage name={props.name} component="span" className="error" />
        </>
    )
}


Enter fullscreen mode Exit fullscreen mode

🖊️ 创建表单对象。

在这里我们将定义我们希望表单的形式,它可以是一个 JSON 文件,但在这个例子中,我将使用一个对象来从一开始就放置类型。

我们创建了一些接口。

首先是 InputProps,其中包含输入框的基本属性。最后是四个属性:
- type,用于确定要渲染哪个组件。-
typeValue ,用于确定要分配给 Yup 实例的数据类型。- options是单选按钮或下拉列表的选项。- validations ,验证规则。

然后我们还有之前在 CustomRadioGroup 和 CustomSelect 中已经使用过的 Opt 接口,您甚至可以创建一个接口文件来重复使用它们。


最后,我们通过 Yup的验证接口来设置验证规则。- type
,是 我们想要应用于该字段的验证类型。- value,我们将设置为验证值(可选)。- message
要显示的自定义消息。



export interface InputProps {
    name: string
    value: string | number | boolean
    placeholder?: string
    label?: string

    type: 'text' | 'radio-group' | 'email' | 'password' | 'select' | 'checkbox'
    typeValue?: 'string' | 'boolean'
    options?: Opt[]
    validations: Validation[]
}

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

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


Enter fullscreen mode Exit fullscreen mode

根据接口,我们导出一个包含表单对象的常量,在本例中,该对象仅包含一个表单。

这里我们创建的正是之前创建过的基本形式。




export const forms: { [x: string]: InputProps[] } =
{
    login: [
        {
            type: "text",
            name: "name",
            placeholder: "Full Name",
            value: "",
            validations: [
                {
                    type: "minLength",
                    value: 3,
                    message: "Min. 3 characters",
                },
                {
                    type: "required",
                    message: "Full Name is required"
                },
            ],

        },
        {
            type: "email",
            name: "email",
            placeholder: "E-mail",
            value: "",
            validations: [
                {
                    type: "required",
                    message: "Email is required"
                },
                {
                    type: "email",
                    message: "Email no valid"
                }
            ],

        },
        {
            type: "password",
            name: "password",
            placeholder: "Password",
            value: "",
            validations: [
                {
                    type: "required",
                    message: "Password is required"
                }
            ],

        },
        {
            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-group",
            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/utils,并在其中创建一个名为getInputs.ts的文件。
我们将在其中编写第一个函数:

generateValidations它只接收一个参数:

  • 字段,第一个是包含所有属性的字段,尽管我们只需要验证和字段的值类型。


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


Enter fullscreen mode Exit fullscreen mode

然后我们需要创建一个空的模式,我们将重新为其赋值。

但目前模式可以是字符串或布尔值,因为唯一的布尔值是复选框,其他值都是字符串,但它将接受其他原始值。



const generateValidations = (field: InputProps) => {
    let schema = Yup[field.typeValue ? field.typeValue : 'string']() // Yup.string() 
}


Enter fullscreen mode Exit fullscreen mode

接下来,我们将逐步进行每个字段的验证。这些验证规则位于field.validations 文件中。



import * as Yup from "yup";
import { InputProps } from './forms';

const generateValidations = (field: InputProps) => {

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

    for (const rule of field.validations) {}
}


Enter fullscreen mode Exit fullscreen mode

然后,我们将评估该字段的验证类型,我们将使用 switch 语句来实现。

默认情况下,只有必需的验证和消息会被添加到架构中。



import * as Yup from "yup";
import { InputProps } from './forms';

const generateValidations = (field: InputProps) => {

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

    for (const rule of field.validations) {
        switch (rule.type) {

            default: schema = schema.required(rule.message); break;
        }
    }

}


Enter fullscreen mode Exit fullscreen mode

最后,我们添加其他验证并返回模式



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

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

const generateValidations = (field: InputProps) => {

    let schema = Yup[field.typeValue ? 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;

            default: schema = schema.required(rule.message); break;
        }
    }

    return schema
}


Enter fullscreen mode Exit fullscreen mode

✏️ 初始化表单值。

现在我们需要初始化表单的值。

为此,我们将创建一个接收参数的函数。

你会注意到,文件中的src/utils/forms.ts常量forms导出了一个对象,这样做的目的是为了让我们现在创建的函数接收一个与该对象键相对应的属性,例如键'login'。这样,我们就可以将表单保存在单个文件中。



export const forms: { [x: string]: InputProps[] } = {
    login: [
        // ...
    ],
    // register:[
        //... 
    // ],
    // etc...
}


Enter fullscreen mode Exit fullscreen mode

另一种方法是将整个表单传递给函数。

但我们将只传递密钥来完成此操作。

于是我们创建了这个函数,并在函数内部初始化了两个变量:

  • initialValues,表单的初始值。
  • validationsFields**,表单中每个字段的验证规则。

这些变量将被重新赋值,因此我们使用 let 并将它们初始化为空对象。



type Form = 'login'

export const getInputs = (section: Form) => {

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

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


Enter fullscreen mode Exit fullscreen mode

然后我们浏览表单,访问其各个部分。



import { forms } from './forms';

type Form = 'login'

export const getInputs = (section: Form) => {

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

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

    for (const field of forms[section]) {}
};


Enter fullscreen mode Exit fullscreen mode

循环内部:
1 - 我们使用 initialValues 变量来计算字段名称并分配字段的默认值。

2 - 我们将设置一个条件,如果该字段没有验证,则直接执行continue,这样它就会退出循环,但继续执行 for 循环之后的其余代码。

3 - 然后我们使用该函数生成我们之前创建的验证方案,并将其赋值给一个常量。

4 - 在循环结束时,我们使用 validationsFields 变量来计算字段名称,并将 schema 变量中生成的 schema 赋值给该字段。

5 - 最后,循环结束后,我们返回一个包含验证规则、表单初始值和输入的对象。



import { forms } from './forms';

type Form = 'login'

export const getInputs = (section: Form) => {

    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,
        inputs: forms[section],
    };
};


Enter fullscreen mode Exit fullscreen mode

整个文件看起来会像这样(如果需要,您可以将其分成不同的部分)👀



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

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

const generateValidations = (field: InputProps) => {

    let schema = Yup[field.typeValue ? 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;
            default: schema = schema.required(rule.message); break;
        }
    }

    return schema
}

type Form = 'login'

export const getInputs = (section: Form) => {

    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,
        inputs: forms[section],
    };

};


Enter fullscreen mode Exit fullscreen mode

🖊️ 使用 getInputs 函数。

我们回到src/pages/FormikDynamic.tsx文件,在组件外部使用getInputs函数,将所需的部分作为参数传递,并获取返回值。



const { initialValues, inputs, validationSchema } = getInputs('login')


Enter fullscreen mode Exit fullscreen mode

初始值和验证方案已分配给Formik组件。



<Formik
    initialValues={initialValues}
    validationSchema={validationSchema}
    onSubmit={(values) => { console.log(values) }}
>
// ...


Enter fullscreen mode Exit fullscreen mode

现在在Form组件内部,我们将使用 map 函数遍历从 getInputs 获取的输入。

我们将使用 switch 语句来判断要渲染的输入类型。根据每种类型,我们会渲染一个组件并传递必要的 props,TypeScript 将在此过程中提供帮助。



<Form noValidate>
{
    inputs.map(({ name, type, value, ...props }) => {
        switch (type) {
            case "select":
                return <CustomSelect
                    key={name}
                    label={props.label!}
                    name={name}
                    options={props.options!}
                />

            case "radio-group":
                return <CustomRadioGroup
                    label={props.label!}
                    name={name}
                    options={props.options!}
                    key={name} />

            case "checkbox":
                return <CustomCheckBox
                    label={props.label!}
                    key={name}
                    name={name}
                />

            default:
                return <CustomTextInput
                    key={name}
                    name={name}
                    placeholder={props.placeholder}
                    type={type}
                />
    }
})
}


Enter fullscreen mode Exit fullscreen mode

该组件看起来会像这样👀:



import { Form, Formik } from "formik"
import { CustomCheckBox, CustomRadioGroup, CustomTextInput, CustomSelect, Layout } from "../components"
import { getInputs } from "../utils"

const { initialValues, inputs, validationSchema } = getInputs('login')

export const FormikDynamic = () => {
    return (
        <Layout title="Formik Dynamic">

            <Formik
                {...{ initialValues, validationSchema }}
                onSubmit={(values) => { console.log(values) }}
            >
                {
                    () => (
                        <Form noValidate>
                            {
                                inputs.map(({ name, type, value, ...props }) => {
                                    switch (type) {
                                        case "select":
                                            return <CustomSelect
                                                key={name}
                                                label={props.label!}
                                                name={name}
                                                options={props.options!}
                                            />

                                        case "radio-group":
                                            return <CustomRadioGroup
                                                label={props.label!}
                                                name={name}
                                                options={props.options!}
                                                key={name} />

                                        case "checkbox":
                                            return <CustomCheckBox
                                                label={props.label!}
                                                key={name}
                                                name={name}
                                            />

                                        default:
                                            return <CustomTextInput
                                                key={name}
                                                name={name}
                                                placeholder={props.placeholder}
                                                type={type}
                                            />
                                    }
                                })
                            }

                            <button className="btn btn_submit" type="submit">Submit</button>
                        </Form>
                    )
                }
            </Formik>
        </Layout>
    )
}


Enter fullscreen mode Exit fullscreen mode

就是这样,我们得到了一个动态表单,只需修改表单文件即可向表单添加另一个表单或另一个字段,添加另一个规则,而无需修改组件。

此外,如果需要,您还可以将 FormikDynamic 组件拆分为更小的组件。

结论。

如果您的应用程序有多个表单,那么实现动态表单将非常有用;而且,借助 Formik 库和 Yup 的验证功能,处理这种表单管理情况就容易得多。

我还可以给你一个建议:想象一下,你有一个 API,它可以返回一个包含字段和验证信息的 JSON 对象,这比使用一个包含所有表单信息的文件要有用得多。

希望你喜欢这篇文章,并且它能帮助你更好地了解如何使用 React 和 Formik 创建动态表单。🤗

如果您知道其他更好或不同的实现此功能的方法,请随时评论🙌。

如果您有兴趣与我合作项目,欢迎查看我的作品集!富兰克林·马丁内斯·卢卡斯

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

✏️ 源代码。

GitHub 标志 Franklin361 /动态表单

使用 React 和 Formik 创建动态表单📝

使用 Formik 和 React JS 构建动态表单。📝

这次,我们将使用 React JS 和 Formik 创建动态表单!

演示

 

特点⚙️

  1. 在表格上显示
  2. 创建动态表单
  3. 字段验证

 

科技🧪

  • ▶️ React JS(版本 18)
  • ▶️ Vite JS
  • ▶️ TypeScript
  • ▶️ 福米克
  • ▶️ CSS 基础版

 

安装🧰

  1. 克隆仓库(需要安装Git)。
    git clone https://github.com/Franklin361/dynamic-form
Enter fullscreen mode Exit fullscreen mode
  1. 安装项目依赖项。
    npm install
Enter fullscreen mode Exit fullscreen mode
  1. 运行项目。
    npm run dev
Enter fullscreen mode Exit fullscreen mode

 

文章链接⛓️

这是教程的链接,如果你想看看的话!👀




文章来源:https://dev.to/franklin030601/dynamic-forms-with-formik-and-react-js-3no1