在 React 中从 JSON 生成动态表单
介绍
在 Web 应用实施中,创建表单是一项无聊但不可避免的任务。在大多数实际应用中,您需要实现至少两个表单(登录、注册)。虽然使用少量表单元素即可实现这一点,但如果应用涉及冗长而复杂的表单,这很快就会让您精疲力竭。
因此,整体构建质量会下降。例如,代码中的验证级别在最后并不存在。表单元素的实现可能很笨拙。为了避免这种情况,我们可以尝试考虑自动化表单的大部分内容。在本指南中,我们将探索一种使用自定义 JSON 模式生成完整 React 表单的方法。
首先,让我们列出预期的功能集。
- 通用表单元素。如果我们看一下普通的 HTML 输入元素,我们会发现使用界面与组件之间存在巨大差异。例如,文本输入创建为<input type="text" />,而选择创建为<select><option></option></select>。因此,我们需要为所有表单元素创建一致的 API。 
- 处理输入和值更改。在 React 中,单独处理表单元素的状态是一项繁琐的任务。如果我们为每个输入项声明处理程序,我们的父组件很快就会被样板代码弄得乱七八糟。为了缓解这种情况,我们的实现应该能够处理父组件中更改的表单输入和提交详细信息。 
- 验证。输入验证和错误反馈是两个最容易被忽视的功能,但对于流畅的用户体验却至关重要。我们的实现应该从本质上支持验证并向用户提供反馈。 
现在期望已经明确,让我们深入实施。
定义表单元素
考虑到上述要求,显然表单状态管理应该在我们的实现中内部管理。您可以从头开始构建它,也可以使用现有库(例如Formik )。在本指南中使用后者。您可以通过运行npm install --save formik来安装 Formik 。
Formik 提供了几个有用的功能,包括:
- 表单状态和输入值变化的内部管理
- 大多数表单元素都具有一致的 API
- 一组可扩展的基础组件,用于创建 Formik 中不可用的自定义组件(例如:datepicker)
- 能够插入验证库
使用 Formik 后,下一步就是定义您的表单元素组件。为简单起见,本指南仅介绍文本输入、下拉选择和提交按钮的实现。但您可以轻松扩展它以支持任何其他表单元素。
      // FormElements.jsx
import React from 'react';
import {
    Formik,
    Form as FormikForm,
    Field,
    ErrorMessage,
    useFormikContext,
    useField,
    useFormik
} from 'formik';
export function Form(props) {
    return (
        <Formik
            {...props}
        >
            <FormikForm className="needs-validation" novalidate="">
                {props.children}
            </FormikForm>
        </Formik>)
}
export function TextField(props) {
    const { name, label, placeholder, ...rest } = props
    return (
        <>
            {label && <label for={name}>{label}</label>}
            <Field
                className="form-control"
                type="text"
                name={name}
                id={name}
                placeholder={placeholder || ""} 
                {...rest}
            />
            <ErrorMessage name={name} render={msg => <div style={{ color: 'red' }} >{msg}</div>} />
        </>
    )
}
export function SelectField(props) {
    const { name, label, options } = props
    return (
        <>
            {label && <label for={name}>{label}</label>}
            <Field
                as="select"
                id={name}
                name={name}
            >
                <option value="" >Choose...</option>
                {options.map((optn, index) => <option value={optn.value} label={optn.label || optn.value} />)}
            </Field>
            <ErrorMessage name={name} render={msg => <div style={{ color: 'red' }} >{msg}</div>} />
        </>
    )
}
export function SubmitButton(props){
    const { title, ...rest } = props;
    const { isSubmitting } = useFormikContext();
    
    return (
        <button type="submit" {...rest} disabled={isSubmitting}>{title}</button>
    )
}
    
如果你不熟悉 Formik 组件,上面的代码可能有点难以理解。简单来说,每个组件的功能如下:
- Formik - 将名为FormikContext的上下文注入子组件。这是保存表单状态的地方。现在所有子组件都可以使用useFormikContext钩子访问它。
- 表单- HTML 表单的包装器。它自动处理提交和重置事件。
- Field - 最重要的。它会自动附加到FormikContext并处理值更改。您只需提供应更新的字段的名称。
- ErrorMessage - 绑定到错误字段并显示错误的组件
除了上述内容之外,你还可以看到每个字段都附带一个标签。这更像是一个设计决定,你可以随意省略它。
完成上述设置后,接下来您可以将它们放入 React 表单并测试功能。以下代码显示了如何在上述元素上创建基本表单。与使用裸金属表单元素相比,该代码非常干净。
      // App.js
import React, { useState } from 'react';
import './App.css';
import { Form, TextField, SelectField, SubmitButton } from './FormElements';
function App() {
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    role: ""
  });
  const onSubmit = (values, { setSubmitting, resetForm, setStatus }) => {
    console.log(values);
    setSubmitting(false);
  }
  return (
    <div className="App">
          <Form
            enableReinitialize
            initialValues={formData}
            onSubmit={onSubmit}
          >
            <div>
              <TextField 
                name="name"
                label="Name"
              />
            </div>
            <div>
              <TextField 
                name="email"
                label="Email"
              />
            </div>
            <div>
              <SelectField 
                name="role"
                label="Role"
                options={[
                  {
                    label: "Admin",
                    value: "admin"
                  },
                  {
                    label: "User",
                    value: "user"
                  }
                ]}
              />
            </div>
            <SubmitButton
              title="Submit"
            />
          </Form>
    </div>
  );
}
export default App;
    
您可以通过单击“提交”按钮并验证值是否按预期返回来测试表单。下一步是配置表单验证和错误处理。尽管表单组件已经定义了错误字段,但对于无效输入不会产生错误。要使其发挥作用,需要一个验证机制。
表单验证
就像上一个场景一样,您可以手动构建验证逻辑,也可以使用第三方库。Yup就是这样一个库,它对 Formik 的支持非常好,因此本指南将使用它。但您可以自由尝试其他库,看看您更喜欢哪个。
使用 Yup,启用验证就像定义 JSON 模式一样简单。以下代码定义了上述表单的模式。定义后,模式可以轻松附加到表单。
      // App.js
// ...
import * as Yup from 'yup';
// ...
const FormSchema = Yup.object().shape({
    name: Yup.string()
          .required('Required')
          .min(5, "Required"),
    email: Yup.string().email()
        .required('Required')
        .min(1, "Required"),
    role: Yup.string().oneOf(['admin', 'user'])
          .required('Required')
          .min(1, "Required"),
});
// ...
    <Form
        enableReinitialize
        validationSchema={FormSchema}
        initialValues={formData}
        onSubmit={onSubmit}
    >
    
    ...
    </Form>
    
您可以尝试输入不同的值并使用架构配置来了解其工作原理。现在表单元素已准备就绪,您可以看到定义初始表单状态和 Yup 架构涉及重复任务。此外,由于表单元素具有一致的 API,因此显然可以生成表单。
从 JSON 生成
生成器的实现很简单。首先,必须有一个约定的 JSON 模式格式,其中包含元素所需的所有信息。下面的代码中使用了一种这样的格式,但您可能需要根据需要对其进行调整。例如,如果您需要自定义错误消息,这可以成为您模式的一部分。
      import React, { useState, useEffect } from 'react';
import './App.css';
import { Form, TextField, SelectField, SubmitButton } from './FormElements';
import * as Yup from 'yup';
const formSchema = {
    name: {
        type: "text",
        label: "Name",
        required: true
    },
    email: {
        type: "email",
        label: "Email",
        required: true
    },
    role: {
        type: "select",
        label: "Role",
        required: true,
        options: [
            {
                label: "Admin",
                value: "admin"
            },
            {
                label: "User",
                value: "user"
            }
        ]
    }
}
function App() {
    const [formData, setFormData] = useState({});
    const [validationSchema, setValidationSchema] = useState({});
    useEffect(() => {   
        initForm(formSchema);
    }, []);
    const initForm = (formSchema) => {
        let _formData = {};
        let _validationSchema = {};
        for(var key of Object.keys(formSchema)){
            _formData[key] = "";
            if(formSchema[key].type === "text"){
                _validationSchema[key] = Yup.string();
            }else if(formSchema[key].type === "email"){
                _validationSchema[key] = Yup.string().email()
            }else if(formSchema[key].type === "select"){
                _validationSchema[key] = Yup.string().oneOf(formSchema[key].options.map(o => o.value));
            }
            if(formSchema[key].required){
                _validationSchema[key] = _validationSchema[key].required('Required');
            }
        }
        setFormData(_formData);
        setValidationSchema(Yup.object().shape({ ..._validationSchema }));
    }
    const getFormElement = (elementName, elementSchema) => {
        const props = {
            name: elementName,
            label: elementSchema.label,
            options: elementSchema.options
        };
        if (elementSchema.type === "text" || elementSchema.type === "email") {
            return <TextField {...props} />
        }
        if (elementSchema.type === "select") {
            return <SelectField  {...props} />
        }
    }
    const onSubmit = (values, { setSubmitting, resetForm, setStatus }) => {
        console.log(values);
        setSubmitting(false);
    }
    return (
        <div className="App">
            <Form
                enableReinitialize
                initialValues={formData}
                validationSchema={validationSchema}
                onSubmit={onSubmit}
            >
                {Object.keys(formSchema).map( (key, ind) => (
                    <div key={key}>
                        {getFormElement(key, formSchema[key])}
                    </div>
                ))}
            </Form>
        </div>
    );
}
export default App;
    
结论
表单是大多数实用 Web 应用中的必备组件。在本指南中,我们探索了一种简化 React 中表单处理的方法。首先,我们通过包装底层 HTML 表单元素创建了一组表单元素。这为我们提供了跨所有元素的统一 API。接下来,我们定义了描述表单元素及其工作方式的规范。最后,我们将两者结合起来,从架构生成表单。在本指南中,我们仅涉及几个组件,但我们可以观察到该方法可扩展到我们将来可能需要的任何自定义表单元素。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
 
                                 
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                 
                             
                                     
                                     
                                     
                                     
     
    
 
             
   
        
请先 登录后发表评论 ~