前言

ErgateForm 是配置化的 antd form(react) 组件。通过配置化生成表单。实现了表单联动、动态表等单功能,表单属性仍沿用 antd form 各个组件的属性。

我自己在做ToB类项目时,比较头疼大量的表单业务。会将表单等组件重新封装一下,使用JSON来配置化自动生成表单。即方便CV,也方便各种抽离,对于模块化也比较友好,下图是我做的DEMO,可以很方便的组合成各种表单

image.png

基础使用

安装

npm install @aotoo/ergateform

# or

yarn add @aotoo/ergateform

基础表单

import ErgateForm from '@aotoo/ergateform'
import {Form}  from 'antd'

function App(){
  const form = Form.useForm()
  const formConfig = {
    labelCol:{ span: 8 }
    wrapperCol: { span: 16 }
    layout: "horizontal"
    initialValues: {textbox: 'hello world'}
    form: form  // antd的form实例
    data: [
      {
        label: '文本框',
        name: 'textbox',
        $input: {
          type: 'text',  // antd Input
        },
      },
    ]
  }

  return (
    <ErgateForm {...formConfig}/>
  )
}

联动表单

import ErgateForm from '@aotoo/ergateform'
import {Form}  from 'antd'

function App(){
  const form = Form.useForm()  // antd的form实例
  const formConfig = {
    labelCol:{ span: 8 }
    wrapperCol: { span: 16 }
    layout: "horizontal"
    initialValues: {textbox: 'hello world'}
    form: form
    data: [
      {
        label: '目标文本框',
        name: 'target-input',
        $input: { type: 'text'},
      },
      {
        label: '响应文本框',
        name: 'response-input',
        $input: { type: 'text' },
        union: {
          target: 'target-input',
          event: 'onChange',
          callback(e){
            console.log(e.target.value)
            form.setFieldValue('response-input', e.target.value)
          }
        }
      },
    ]
  }

  return (
    <ErgateForm {...formConfig}/>
  )
}

联动结构

import ErgateForm from '@aotoo/ergateform'
import {Form}  from 'antd'

function App(){
  const form = Form.useForm()
  const formConfig = {
    labelCol:{ span: 8 }
    wrapperCol: { span: 16 }
    layout: "horizontal"
    initialValues: {textbox: 'hello world'}
    form: form  // antd的form实例
    data: [
      {
        label: '文本框',
        name: 'select-box',
        $input: {
          type: 'select',
          options: [
            {label: '选项一', value: 'option-1'},
            {label: '选项二', value: 'option-2'},
            {label: '选项三', value: 'option-3'},
          ]
        },
      },
      
      union('select-box', function(value){
        if (value === 'option-2') {
          return JSX
        }
      })
    ]
  }

  return (
    <ErgateForm {...formConfig}/>
  )
}

联动状态

import ErgateForm from '@aotoo/ergateform'
import {Form}  from 'antd'

function App(){
  const form = Form.useForm()
  const formConfig = {
    labelCol:{ span: 8 }
    wrapperCol: { span: 16 }
    layout: "horizontal"
    initialValues: {textbox: 'hello world'}
    form: form  // antd的form实例
    state: { visible: false },
    data: function(state, setState){
      return [
        <button onClick={function(){
          setState({
            visible: true
          })
        }}>按钮 </button>,
        
        union('state.visible', function(value){
          if (value) {
            return JSX
          }
        })
      ]
    }
  }
  
  

  return (
    <ErgateForm {...formConfig}/>
  )
}

支持的表单类型

{
  $input: { type: '???', }  // type 用来设置表单类型
}
  • button => Button
  • text => Input 表单的别名
  • textarea => Input.TextArea 别名
  • search => Input.Search
  • password => Input.Password
  • cascader => Cascader
  • select => Select
  • autocomplete => AutoComplete
  • inputnumber => InputNumber
  • rate => Rate
  • slider => Slider
  • switch => Switch
  • timepicker => TimePicker
  • timerange => TimePicker.RangePicker 别名
  • treeselect => TreeSelect
  • datepicker => DatePicker
  • daterange => DatePicker.RangePicker 别名
  • checkbox => Checkbox
  • checkboxGroup => Checkbox.Group
  • radio => Radio
  • radioGroup => Radio.Group
  • transfer => Transfer
  • upload => Upload

配置

Form配置

ErgateForm中使用最外层的非data属性来配置Form属性

Form.state
这是ErgateForm的属性,原生antd Form表单无此属性,该属性作用是用来方便模块化时能够方便设置状态值得变更,使用React的useState实现

ergateform的写法

const formConfig = {
  labelCol:{ span: 8 }
  wrapperCol: { span: 16 }
  layout: "horizontal"
  ...
  data: [...]  // data用来配置 Form.Item 集合
}

return <ErgateForm {...formConfig}/>

// 或者

return <ErgateForm
  labelCol= {{span: 8}}
  wrapperCol={{span: 16}}
  layout={'horizontal'}
  ...
  data: [...]
/>

antd form 原生写法

<Form 
  labelCol={{ span: 8 }} 
  wraperCol={{ span: 16 }} 
  layout="horizontal"
  ...
>
  ...
</Form>

FormItem配置

Form.Item 是antd表单的基础结构。ErgateForm使用data的数据项来配置Form.Item属性及表单属性

ergateform的写法

  1. data为数组

    // 配置
    {
      data: [
     {
       label: '标题名',  // 对应 Form.Item 的label属性
       name: 'uniq-name' // 对应 Form.Item 的name属性
       $input: { ... } // 配置表单属性
     },
     JSX,
     union(...)
      ]
    }
  2. data为方法
    可以将data设置为方法,只要返回数组项配置即可。该方法接收state, setState两个参数,这样我们可以在配置中灵活设置一些状态
data: function(state, setState){
  return [
    ...
  ]
}
数据项可以是JSX,或者union方法返回的结构,union方法后面会讲到

antd form 原生写法

<Form.Item 
  label="标题名" 
  name="uniq-name"
>
  <input {...$input} />
</Form.Input>

横向排列

ErgateForm 默认使用Space组件来横向排列表单,只需要将$input配置成数组即可,来看配置

{
  data: [
    {
      ...,  // 此时这里的属性会自动添加到Space组件中
      $input: [
        {
          label: 'UserName',
          name: 'username',
          $input: {  // 嵌套表单仍然使用`$input`属性
            type: 'text',
          }
        },
        {
          label: 'Password',
          name: 'password',
          $input: {
            type: 'password'
          }
        }
      ]
    }
  ]
}
radio / checkbox 的子项默认是横向排列,如果需要竖向排列时,官网是使用Space组件包裹子项,在这里直接配置$input.direction就好了

特殊属性

ErgateFome加入的属性,用来控制配置结构和联动

  1. $input
  2. union

$input

$input用来配置具体的表单/表单组,如 Input, Select等支持的表单元件,api 属性与官网一致

$input.type

该属性用来标识使用那个表单组件,

antd的Button组件包含type属性,请使用 buttonType 替换

union

用来设置表单联动,union的实现思路有点类似于监视者的角色,当目标值变更时及时做出响应。union属性包含三个必须设置的参数 target、event、callback

union.target

描述对齐的目标name

union.event

表单组件一般都有几个事件方法,例如 Search 表单组件有onSearch和onChange等事件,我们只想关注onChange事件时将union.event设置为onChange即可

union.callback

事件响应方法,当对齐目标表单状态发生改变时,触发该方法

多联动

一个表单需要关注多个状态变更时设置,将union设置为数组即可实现多联动

示例 code

[
  {
    label: '我是目标表单',
    name: 'name-target',
    $input: {type: 'select', ...}
  },
  
  // 单联动
  {
    label: '单联动响应表单',
    name: 'name-response',
    union: {
      target: 'name-target',
      event: 'onChange',
      callback(value, option){
        /** do something */
      }
    }
  },
  
  // 多联动
  {
    label: '多联动响应表单',
    name: 'mul-response',
    union: [
      {target: 'name-target', event: 'onChange', callback: ...},
      {target: 'name-target', event: 'onSearch', callback: ...},
    ]
  }
]

union方法

在设计表单时,有些结构需要根据状态来显示,此时可以引入union方法来实现这种类型的需求。

三种用法

union有三种设置方法

  1. 表单值响应
    原理是通过antd的form.useWatch对观测表单的状态改变做出响应。需要注意,如果表单组件不包含value属性,会提示报错。例如 Checkbox 组件就不包含 value 属性,需要通过state来控制Checkbox的状态。幸运的是其他所有表单都有value属性
  2. state值响应
  3. 静默响应,但会在表单完成时反馈出结构(useEffect实现)
// 联动组件
union('target-name', function(value){
  return JSX
});
// 联动state
union('state.xxx', function(value){
  return JSX
})
// 无响应
union('任意字串描述,不可以和组件name/state[name]重名即可', function(value){
  return JSX
})
union方法中也可以设置其他表单组件的属性,但一定要加上延迟,否则会造成渲染冲突,这一点后面会讲到

示例CODE

注意下面的union方法的使用的位置

import ErgateForm {union} from '@aotoo/ergateform'
import {Form} from 'antd'

function App(){
  const form = Form.useForm()
  const formConfig = {
    ... // Form配置项
    data: function(state, setState){
      return [
        {
          $input: [ // 表单组
            {
              label: '文本框',
              name: 'target-select',
              $input: {
                type: 'select',
                options: [
                  {label: '选项一', value: '1'},
                  ...
                ]
              }
            },
            // 组内union
            union('target-select', function(value){
              if (value === '3'){
                return <div>response vlaue 3</div>
              }
            })
          ],
        },

        <button>按钮</button>,  // 支持直接插入JSX

        // 组外union
        union('target-input', function(value){
          if (value === '1') {
            return <div>response value 1</div>
          }
          if (value === '2') {
            return <div>response value 2</div>
          }
        }),
        
        // state联动
        union('state.xxx', function(value){
          return JSX
        })
      ]
    }
  }

  return <ErgateForm {...formConfig}/>
}

return <App />

union 方法不需要设置 event 参数,内部使用了 antd 的 Form.useWatch 实现值变更追踪

暂时不支持 async await promise 的使用

复杂使用

formItem方法

引入formItem方法,它用来创建单独的Form.Item结构,也可以用来创建联动表单结构

import {formItem, union} from '@aotoo/ergateform';

<ErgateForm
  ...
  data={[
    {..., name: 'select-box', $input: {type: 'select', options: [...]}},
    union('select-box', function(value){
      if (value === '???' ) {
        return formItem({
          label: '新表单',
          ...,
          $input: { type: 'text'}
        })
      }
    })
  ]}
/>

formList方法

引入formList方法,它用于创建一组动态增、删的表单组,formList可以参考antd官方的原版例子,并未做更多的修改

import {formItem, union, formList} from '@aotoo/ergateform';

<ErgateForm
  ...
  data={[
    {..., name: 'select-box', $input: {type: 'select', options: [...]}},
    union('select-box', function(value){
      if (value === '???' ) {
        return formList({...}).render((fields, {add, remove}, {errolist}) => {
          return (
            <>
              {
                fields.map((field)=>{
                  return <input {...field}/>
                })
              }
              <button onClick={add()} >新增</button>
            </>
          )
        })
      }
    })
  ]}
/>

getForm方法

Ergate使用的是antd的最新版本(5),可以使用form的实例来做很多事情,getForm方法是为了模块化时能方便取到form实例,ErgateForm会拦截每个表单事件的回调方法,重构后并还原成原来的使用方式

 data = [
   {
     label: 'title',
     name: 'name',
     $input: {
       type: 'button',
       buttonType: '...',  // 注意button组件是没有value的,不能够使用union来追踪
       onClick(){
         const form = this.getForm()  // 获取form的实例
         form.setFieldValue('xxx-name', value)
       }
     }
   }
 ]

setOptions方法

setOptions这个方法是ergateform的扩展方法,可以动态设置Form中各个Select组件的下拉选项

data = [
   {
     label: 'title',
     name: 'select-name',
     $input: {
       type: 'select',
       options: []
     }
   },
   
   {
     $input: {
       type: 'button',
       onClick(){
         const form = this.getForm()
         form.setOptions('select-name', [
           {label: '选项一', value: '1'},
           {label: '选项二', value: '2'},
           ...
         ])
       }
     }
   }
 ]

union 方法中设置其他表单属性

不建议在 union 的回调方法中设置其他表单的属性,如果非要设置请加上延迟方法,否则会造成渲染冲突
示例 code

{
  data: [
    ...,

    {
      label: '选择框',
      name: 'select-box',
      $input: {
        options: [
          {lable: 'a', value: 'a'},
          {lable: 'b', value: 'b'},
        ]
      }
    },

    union('select-box', function(value){
      const form = this.getForm()
      if (value === 'b') {
        setTimeout(()=>{
          form.setOptions('select-box', [...])
        }, 100)
      }
      return JSX || null
    })
  ]
}

使用state


const stateData = {
  checked: false
}

const ergateFomrConfig = {
  layout: "horizontal"
  onFinish: onFinish,
  ...
  state: stateData,
  data: function(state, setState){
    return [
      {
        name: 'check-box',
        $input: {
          type: 'checkbox',
          checked: state.checked,
          onChange(){
            setState({
              checked: !state.checked
            })
          }
        }
      },
      
      union('state.checked', function(value){
        console.log(value)
      })
    ]
  }
}

天天修改
28 声望3 粉丝

大前端,小程序,全栈开发