基于 React Redux 打造可配置的通用业务表单(附源码)

随着 Web 应用的不断发展,业务表单作为 Web 应用的重要组成部分之一,在 Web 应用中的作用越来越被重视。然而,开发表单页面过程中,总是需要不断地去实现各种各样的表单、表单组件,导致代码重复,效率低下。因此,我们需要一种可配置的通用业务表单,我们可以在需要的时候对其进行配置,快速生成业务表单页面,提高开发效率。

在本文中,我们将介绍如何基于 React Redux 打造可配置的通用业务表单,这个表单可以用于各种业务场景,满足各种需求。接下来,我们将逐一介绍这个表单的实现过程。

设计数据结构

对于一个可配置的通用业务表单,我们需要对其数据结构进行设计。下面是我们设计的数据结构:

{
  // 表单属性
  formProps: {
    // 表单名称
    title: '',
    // 提交按钮文本
    submitText: '',
    // 表单提交接口地址
    apiUrl: '',
    // 表单提交方法
    method: 'POST',
    // 表单提交成功回调
    successCallback: () => {},
    // 表单提交失败回调
    failCallback: () => {},
  },
  // 表单字段
  fields: [
    {
      // 字段名称
      name: '',
      // 字段类型
      type: '',
      // 字段默认值
      defaultValue: '',
      // 字段校验规则
      rules: [],
      // 字段属性
      props: {},
      // 字段选项
      options: [],
      // 子表单,用于嵌套表单
      subForm: {},
    },
    ...
  ],
}

在数据结构设计中,我们用 formProps 存储表单属性,用 fields 存储表单字段。其中,fields 是一个数组,每个元素表示一个表单字段。name 表示字段名称,type 表示字段类型,defaultValue 表示字段默认值,rules 表示字段校验规则,props 表示字段属性,options 表示字段选项,subForm 用于嵌套表单。

实现表单组件

在数据结构设计完成后,我们需要实现表单组件。这里我们使用 Antd 组件库来实现表单组件。下面是完整的表单组件代码:

import React from "react";
import { Form, Input, Button, Select } from "antd";
import { connect } from "react-redux";
import axios from "axios";

const FormItem = Form.Item;
const Option = Select.Option;

class DynamicForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = { loading: true };
  }

  componentDidMount() {
    const { fetchData } = this.props;
    fetchData && fetchData().then(() => this.setState({ loading: false }));
  }

  validateField = (value, rules) => {
    let errorMsg = "";
    for (let i = 0; i < rules.length; i++) {
      const rule = rules[i];
      if (rule.required && !value) {
        errorMsg = "该字段为必填项";
      } else if (rule.maxLength && value && value.length > rule.maxLength) {
        errorMsg = `该字段最多输入${rule.maxLength}个字符`;
      }

      if (!!errorMsg) {
        break;
      }
    }
    return errorMsg;
  };

  validateForm = values => {
    const { fields } = this.props;
    let errorFields = [];
    fields.forEach(field => {
      const { name, rules } = field;
      const value = values[name];
      const errorMsg = this.validateField(value, rules);
      if (!!errorMsg) {
        errorFields.push({ name, errorMsg });
      }
    });

    return errorFields;
  };

  handleSubmit = e => {
    e.preventDefault();
    const { form, onSubmit, successCallback, failCallback } = this.props;
    form.validateFields((err, values) => {
      if (!!err) {
        console.log("err", err);
        return;
      }
      const errorFields = this.validateForm(values);
      if (errorFields.length > 0) {
        console.log("errorFields", errorFields);
        return;
      }
      onSubmit &&
        onSubmit(values).then(() => {
          successCallback && successCallback(values);
        }).catch(() => {
          failCallback && failCallback(values);
        });
    });
  };

  renderField = (field, getFieldDecorator) => {
    const {
      name,
      type,
      defaultValue,
      rules,
      props = {},
      options = [],
      subForm = {},
    } = field;
    let fieldNode = null;
    switch (type) {
      case "input":
        const { prefix, suffix, addonBefore, addonAfter, ...otherProps } = props;
        fieldNode = (
          <Input
            placeholder=""
            defaultValue={defaultValue}
            prefix={prefix}
            suffix={suffix}
            addonBefore={addonBefore}
            addonAfter={addonAfter}
            {...otherProps}
          />
        );
        break;
      case "select":
        fieldNode = (
          <Select defaultValue={defaultValue}>
            {options.map(option => (
              <Option value={option.value} key={option.value}>
                {option.label}
              </Option>
            ))}
          </Select>
        );
        break;
      case "subForm":
        const { formProps, fields } = subForm;
        fieldNode = <DynamicForm formProps={formProps} fields={fields} />;
        break;
      default:
        break;
    }
    if (!fieldNode) {
      return null;
    }
    const { label, hidden = false } = props;
    const validateRules = rules.map(rule => ({ required: !!rule.required, message: rule.message }));

    return (
      <FormItem key={name} label={label} style={{ display: hidden ? "none" : "" }}>
        {getFieldDecorator(name, {
          rules: validateRules,
          initialValue: defaultValue,
        })(fieldNode)}
      </FormItem>
    );
  };

  render() {
    const { loading } = this.state;
    const { formProps, fields = [], form } = this.props;
    const { getFieldDecorator } = form;
    const { title, submitText, apiUrl, method } = formProps;

    return (
      <Form onSubmit={this.handleSubmit}>
        <h2>{title}</h2>
        {fields.map(field => this.renderField(field, getFieldDecorator))}
        <FormItem>
          <Button type="primary" htmlType="submit" loading={loading}>
            {submitText || "提交"}
          </Button>
        </FormItem>
      </Form>
    );
  }
}

const mapStateToProps = () => ({});

const mapDispatchToProps = (dispatch, props) => ({
  onSubmit: values => {
    const { apiUrl, method = "POST" } = props.formProps || {};
    return axios({
      method,
      url: apiUrl,
      data: values,
    });
  },
});

const WrappedDynamicForm = Form.create()(connect(mapStateToProps, mapDispatchToProps)(DynamicForm));

export default WrappedDynamicForm;

使用动态表单

使用动态表单,只需要在组件的 render 函数中传入数据即可生成表单。下面是一个使用示例:

import React from "react";
import WrappedDynamicForm from "./components/dynamic-form";

const data = {
  formProps: {
    title: "添加用户",
    submitText: "提交",
    method: "POST",
    apiUrl: "/api/user",
    successCallback: () => {
      alert("添加用户成功");
    },
    failCallback: () => {
      alert("添加用户失败");
    },
  },
  fields: [
    {
      name: "username",
      type: "input",
      props: {
        label: "用户名",
        required: true,
        maxLength: 20,
      },
      rules: [
        { required: true, message: "用户名不能为空" },
        { maxLength: 20, message: "用户名长度不能超过20个字符" },
      ],
    },
    {
      name: "password",
      type: "input",
      props: {
        label: "密码",
        required: true,
      },
      rules: [{ required: true, message: "密码不能为空" }],
    },
    {
      name: "sex",
      type: "select",
      props: {
        label: "性别",
        required: true,
      },
      options: [
        { label: "男", value: "男" },
        { label: "女", value: "女" },
      ],
      rules: [{ required: true, message: "性别不能为空" }],
    },
  ],
};
class App extends React.Component {
  render() {
    return (
      <div>
        <WrappedDynamicForm {...data} />
      </div>
    );
  }
}
export default App;

在使用这个表单组件时,只需要将数据传入组件即可快速生成表单。数据结构清晰,易于扩展和配置,因此可以应用于多种业务场景中。

总结

在本文中,通过对数据结构和代码实现的介绍,我们学习了如何基于 React Redux 打造可配置的通用业务表单,这个表单可以用于各种业务场景,满足各种需求。通过这样的表单组件,我们可以快速构建出一个业务功能完备、可扩展的表单页。希望这篇文章能够对各位前端开发者在实际工作中有所启发,让你的工作更加高效。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65a25f7cadd4f0e0ffa8287a


纠错反馈