博客 / 詳情

返回

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)
      })
    ]
  }
}
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.