二次封装Antd中的Form,根据配置项生成表单

11,349 阅读2分钟
原文链接: github.com

下图这种表单在后台管理项目中很常见,为了能一天写999个表单,我认真的研究了下如何根据配置项直接生成表单(严肃脸)。
image

配置项中需要有哪些字段?

每一个表单项必需的值有keylabel,下面这些为非必需的:

  • required字段(该字段是否必填)
  • component字段(默认的输入组件为Input,如果有component则用传入的component作为输入组件)
  • rules字段(校验规则,默认只会校验是否必填)
  • options字段(与Antd中Form的getFieldDecorator函数接受的options定义一致)

举例:

// formItems.js
import React from 'react'
import { Input, DatePicker, Radio, Select, Checkbox, Switch } from 'antd'

const { TextArea } = Input
const RadioGroup = Radio.Group
const RadioButton = Radio.Button
const Option = Select.Option
const CheckboxGroup = Checkbox.Group
const RangePicker = DatePicker.RangePicker

const selectExample = (
  <Select>
    <Option value="option1">option1</Option>
    <Option value="option2">option2</Option>
    <Option value="option3">option3</Option>
  </Select>
)
const radioGroupExample = (
  <RadioGroup
    options={[
      { label: 'radio1', value: 'radio1' },
      { label: 'radio2', value: 'radio2' },
      { label: 'radio3', value: 'radio3' },
    ]}
  />
)
const radioButtonGroupExample = (
  <RadioGroup>
    <RadioButton value="radio1">radio1</RadioButton>
    <RadioButton value="radio2">radio2</RadioButton>
    <RadioButton value="radio3">radio3</RadioButton>
  </RadioGroup>
)
const checkboxGroupExample = (
  <CheckboxGroup
    options={[
      { label: 'checkbox1', value: 'checkbox1' },
      { label: 'checkbox2', value: 'checkbox2' },
      { label: 'checkbox3', value: 'checkbox3' },
    ]}
  />
)

export default [
  {
    label: 'Input',
    key: 'Input',
    required: true,
  },
  {
    label: '密码输入框',
    key: 'password',
    component: <Input type="password" />,
    rules: [
      {
        required: true,
        pattern: /^[0-9a-zA-Z]{8,16}$/,
        message: '密码长度为8-16位,只能包含数字和英文',
      },
    ],
  },
  {
    label: 'TextArea',
    key: 'TextArea',
    component: <TextArea />,
  },
  {
    label: 'Select',
    key: 'Select',
    required: true,
    component: selectExample,
  },
  {
    label: 'RadioGroup',
    key: 'RadioGroup',
    required: true,
    component: radioGroupExample,
    options: { initialValue: 'radio1' },
  },
  {
    label: 'RadioButtonGroup',
    key: 'RadioButtonGroup',
    required: true,
    component: radioButtonGroupExample,
    options: { initialValue: 'radio2' },
  },
  {
    label: 'CheckboxGroup',
    key: 'CheckboxGroup',
    required: true,
    component: checkboxGroupExample,
    options: { initialValue: ['checkbox3'] },
  },
  {
    label: 'DatePicker',
    key: 'DatePicker',
    required: true,
    component: <DatePicker />,
  },
  {
    label: 'RangePicker',
    key: 'RangePicker',
    required: true,
    component: <RangePicker />,
  },
  {
    label: 'Switch',
    key: 'Switch',
    component: <Switch />,
    options: { valuePropName: 'checked', initialValue: false },
  },
]

封装Form

// MyForm.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Form, Input } from 'antd'

const FormItem = Form.Item
// 默认的layout
export const defaultLabelColSpan = 6
const defaultFormItemLayout = {
  labelCol: { span: defaultLabelColSpan },
  wrapperCol: { span: 14 },
}

// 渲染单个表单项
const renderFormItem = ({ item, layout, getFieldDecorator }) => {
  const { label, key, required, component, options = {}, rules } = item
  return (
    <FormItem key={key} label={label} {...layout}>
      {getFieldDecorator(key, {
        ...options,
        rules: rules || [{ required, message: `${label}为空` }],
      })(component || <Input />)}
    </FormItem>
  )
}

class MyForm extends Component {
  render() {
    // items格式即为上文配置的表单项
    const { items, layout, form: { getFieldDecorator } } = this.props
    return (
      <Form>
        {items.map(item => renderFormItem({ item, layout, getFieldDecorator }))}
      </Form>
    )
  }
}

MyForm.propTypes = {
  items: PropTypes.array.isRequired,
  layout: PropTypes.object,
  form: PropTypes.object.isRequired,
}

MyForm.defaultProps = {
  layout: defaultFormItemLayout,
}

export default Form.create()(MyForm)

根据配置项生成表单页面

在写好MyForm这个组件后,我们根据安装格式定义好的formItems即可快速生成表单页面。

// App.js
import React, { Component } from 'react'
import moment from 'moment'
import { Button } from 'antd'
import MyForm, { defaultLabelColSpan } from './MyForm'
// formItems即为表单的配置项
import formItems from './formItems'

// 模拟发请求(在做修改操作时,表单需要先填充已有数据,这里写了个假的获取详情接口)
const requestDetail = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        Input: 'Input',
        password: 'password',
        Select: 'option2',
        RadioGroup: 'radio2',
        RadioButtonGroup: 'radio2',
        CheckboxGroup: ['checkbox2'],
        DatePicker: '2018-05-15T13:36:27.132Z',
        RangePicker: ['2018-04-15T13:36:27.132Z', '2018-05-15T13:36:27.132Z'],
        Switch: true,
      })
    }, 1500)
  })
}

class App extends Component {
  constructor(props) {
    super(props)
    this.formRef = React.createRef()
  }

  getDetail = () => {
    requestDetail().then(res => {
      // 如果字段的值是日期,要先转成moment格式
      res.DatePicker = moment(res.DatePicker)
      res.RangePicker = res.RangePicker.map(d => moment(d))
      this.formRef.current.setFieldsValue(res)
    })
  }

  onClickSubmit = () => {
    this.formRef.current.validateFieldsAndScroll((err, values) => {
      console.log(values)
      if (err) {
        return
      }
      console.log('校验通过')
    })
  }

  render() {
    return (
      <div>
        <Button style={{ margin: 24 }} type="primary" onClick={this.getDetail}>
          模拟请求数据然后设置表单值
        </Button>

        <MyForm ref={this.formRef} items={formItems} />

        <Button
          style={{ marginLeft: `${defaultLabelColSpan / 24 * 100}%` }}
          type="primary"
          onClick={this.onClickSubmit}
        >
          提交
        </Button>
      </div>
    )
  }
}

export default App

完。