antd form的配置化封装,让表单联动更简单

前言

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 粉丝
0 条评论
推荐阅读
组件库按需引入深入探讨
但这在需要导入非常多的组件场景时,开发繁琐,体验不友好。在这些组件库的官方文档或者社区会推荐一些babel插件,帮助达到良好的开发体验和性能优化。

joyerli1阅读 2.1k

vue3公司自用项目实战入门(vue3+router4+antdv+pinia实现)
一.为什么学typescriptvar三大框架都是用ts写的,做个类比,如果你不知道阿拉伯数字和加减法,怎么解应用题?同理不会ts如何使用vue开发工作项目?二.开发环境准备1.安装node,[链接],下载安装,一路默认下一步 ,...

彬哥头发多2阅读 571评论 1

封面图
前端实现切换主题方案
前端开发人员面临着制作增强用户体验和适应用户偏好的用户界面的任务。带有 css 的 react 主要可用于创建多色可切换主题。为用户提供了在给定时间点在主题颜色之间切换以适合他们的偏好的特权。

MangoGoing1阅读 1.1k评论 2

封面图
Document Redirect 与 XHR Redirect区别
情景复现某天正式环境有用户反馈某页面操作没有任何响应,SRE接收到反馈后,对问题分析复现,复现步骤如下:用户登录商家工作台后复制页签,开启了两个页签,其中一个页签退出登录,另一个页签点击操作另外,SRE...

记得要微笑阅读 691

angular中使用antd的tooltip,主动关闭/打开toolTip框的操作办法
在绝大部分情况下,antd的tooltip都表现的很好,十分好用,但是在一些tooltip在需要跳转到其他页面的按钮上的时候,跳转以后tooltip也会有持续存在,不消失的问题,所以这时候就需要能够在点击的时候主动关闭tooltip

munergs1阅读 653评论 2

封面图
[React] Antd "Same 'key' exist in the Tree" 报错解决
报错大概是上图这个样子,在我的场景中treeData是由后端传输的数据生成的tree的层级是 数据库 -&gt; 表key 分别为数据库id (dbId),表id(tableId) 在生成数据时,数据库层级的id 与 表层级的id相同了,导致组件报...

DiracKeeko阅读 545

element的Form表单就应该这样用
设计目标配置化我们希望把表格的内容,验证规则,甚至于表单的样式,格式都能更规则化,配置化,这样后续我们可以通过构造json去实现一个表单,甚至可用实现拖拽式的构造表单。参数简单尽量减少json的层级,减少js...

视角线阅读 424

封面图

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

28 声望
3 粉丝
宣传栏