JSON表單是一個基於React的抽象組件,它能夠把JSON數據格式描述的表單轉換成項目中的表單,它能夠用簡短的幾行代碼,快速的生成Form表單。
JSON表單的優勢是:react
1:代碼量龐大,開發效率低
每次開發一個表單頁的時候,都須要重複編寫表單組件及其交互事件的代碼,這塊代碼重複編寫且與主線業務邏輯無關,除此以外,表單的校驗、緩存等額外功能,也須要很多的代碼量,這樣就形成了一個表單頁的代碼量龐大。git
2:不便於抽離和進一步的抽象
在一個表單頁內,每每會將表單數據、表單組件、控制邏輯雜糅在一塊兒,當發現某一個子功能在不少場景下都須要用到的時候,想把該子功能拆分出來發現並不容易,由於邏輯、數據、視圖的雜糅,致使想把子功能不受影響的剔除出來須要很仔細的檢查,這樣就致使功能的抽離和抽象的不便github
3:維護成本高
這個和第二個問題是一樣的緣由,這也是個人親身經歷,當我在項目中想優化一個小功能的時候,發現不只把以前的邏輯改沒了,還引出了很多的bug,這致使在一個邏輯很複雜的表單裏,維護變成了一件高危工做。npm
4:須要額外處理校驗和緩存等功能json
其github
地址爲:json_transform_form。數組
npm i json_transform_form --save
import Form from 'json_transform_form' const config = { formKey: 'example-form', data: { name: '', descr: '', typeName: '' }, config: [ { type: 'input', dataKey: 'name', label: 'param', placeholder: '請輸入param', validate: ['required', /^[a-zA-Z_{}0-9]+$/g], style: { display: 'inline-block', width: 270, }, }, { type: 'select', dataKey: 'typeName', options: ['string', 'integer', 'float'], style: { display: 'inline-block', width: 100, margin: '0 15px' }, validate: [{type: 'required', message: 'param類型不能爲空'}] }, { type: 'textarea', dataKey: 'descr', placeholder: '請輸入param含義', label: 'param含義', validate: ['required'], style: { width: 385, } }, ] } <From ref={ref => this.FormWrap = ref} config={config}></From>
上面是用JSON描述的三個經常使用的表單組件組合成的表單,其效果圖以下:緩存
{ formKey: 'paramAddForm', data: {}, config: [] }
屬性 | 是否必傳 | 說明 | 類型 | 默認值 |
---|---|---|---|---|
formKey | 否 | 用來自動緩存,localStorage的key,不傳表示不自動緩存 | string | - |
className | 否 | 用來添加一些自定義樣式 | string | - |
data | 是 | 表單的提交數據,有自動緩存和校驗功能 | object | - |
assisData | 否 | 用於表單控制邏輯的額外數據 | object | - |
config | 是 | 組件配置,表單組件的配置 | Array | - |
realTimeSubmit | 否 | 表單是否實時提交,通常用於篩選表單 | boolean | false |
{ type: 'input', dataKey: 'name', label: 'param', validate: ['required'], style: {} }
屬性 | 是否必傳 | 說明 | 類型 | 默認值 |
---|---|---|---|---|
type | 是 | 表單組件的類型,其值能夠爲: input、select、textarea、form_array、container和一些自定義表單組件 | string | - |
dataKey | 否 | 指定表單組件值的key,能夠爲param.name.firstName形式 | string | - |
label | 否 | 表單組件的label | string | - |
placeholder | 否 | 表單組件的placeholder | string | - |
validate | 否 | 表單組件的校驗規則 | Array | - |
style | 否 | 表單組件的佈局樣式 | string | - |
options | 否 | 當表單組件爲select時,須要傳入的options | Array | - |
render | 否 | 當type爲container時,爲自定義組件,render爲渲染方法 | Function | - |
preventSubmit | 否 | 當realTimeSubmit爲true時,控制當前表單組件是否實時提交 | boolean | false |
children | 否 | 當type爲form_array時,children表示子組件配置列表 | Array | - |
modifyDataFn | 否 | 當type爲自定義組件時,且須要覆蓋render方法中的提交數據方法,可使用modifyDataFn來從新自定義提交數據 | Function | - |
type是用來惟一表示表單組件類型的字段,其中JSON表單提供了三種默認的表單組件:input、select、textarea,還有兩種複雜類型的表單組件:form_array、container。antd
form_array表單組件表示其數據結構爲Array,含有增長項刪除項的複合表單組件,該表單組件的配置裏多一個children的字段,裏面是每一項裏面的表單組件配置的集合,其表單組件的效果以下圖所示:數據結構
container是用來自定義表單的接口,具體用法參考下面具體的介紹。frontend
validate是校驗表單組件數據正確性的字段,其值爲數組,裏面的數組元素能夠爲String、object、RegExp、Function。
JSON表單採用的是async-validator
異步處理校驗,在JSON表單內部會將validate傳入的校驗關鍵字解析爲async-validator
的rules。因此validate數組元素若是爲object的話,其內容就是async-validator
的rules。
1. 數組元素爲string,其值能夠爲: string,值必須爲string number,值必須爲數字 required,值不能爲空 boolean,值必須爲布爾值 integer,值必須爲整數形 float,值必須爲浮點型 email,值必須爲郵箱類型 2. 數組元素爲object,其值爲rules: {type: 'enum', enum: ['1', '2'], message: '值不在肯定範圍內'} 3. 數組元素爲RegExp, validate: [/^[a-zA-Z_{}0-9]+$/g] 4. 數組元素爲Function, validate: [ (rules, value, callback) => {}]
用來肯定表單組件在表單內的佈局樣式,好比想讓表單組件行內顯示,且寬度爲200,其style值以下:
{ display: 'inline-block', width: 200 }
container表單組件是用來自定義表單組件的,它主要的做用有如下幾點:
import { Input, Select } from 'antd' const Option = Select.Option { type: 'container', dataKey: 'descr', style: { display: 'inline-block', width: 100, margin: '0 15px' }, options: ['string', 'integer', 'float'], render: (curData, config, {changeFn, getFocus, loseFocus, error}) => { return <Select value={curData} style={{width: '100%', height: 35}} onMouseEnter={getFocus} onChange={(value) => changeFn(value, () => { loseFocus() })}> { config.options && config.options.map((item, idx) => <Option key={idx} value={item}>{item}</Option>) } </Select> } },
container表單組件只是多一個render渲染方法,裏面能夠自定義表單組件的渲染內容,render方法提供以下參數:
1. curData: 當前container組件的值,跟dataKey相關 2. config: 當前container組件的配置 3:{changeFn, changeDataFn, getFocus, loseFocus, error, JSONForm} changeFn, changDataFn是提交數據的方法,changeFn只能修改當前組件dataKey的值,changeDataFn能夠修改data中任意字段的值,changeFn(value, [callback]), changeFn(dataKey, value, [callback]) getFocus,loseFocus是自定義處理校驗的字段,loseFocus是開始校驗,getFocus是去掉校驗的報錯信息 error是校驗結果的報錯信息 JSONForm是在container中使用JSON表單的組件配置用來生成新的表單組件,意思裏container中依然能夠嵌套表單組件。
JOSN表單只提供了input、select、textarea三種默認的表單組件,遠遠不夠真實的項目中使用,因此咱們能夠將antd組件庫中的組件封裝到JSON表單中,這樣咱們就能夠再項目中很快的使用antd中的組件。
antd-components.js
import React from 'react' import { Input } from 'antd' export default [ { type: 'antd-input', render: (curData, config, {changeFn, getFocus, loseFocus, error}) => { return <Input value={curData} onFocus={getFocus} onBlur={loseFocus} placeholder={config.placeholder ? config.placeholder : ''} style={{borderColor: !!error ? '#f5222d' : ''}} onChange={event => changeFn(event.target.value)} /> } } ]
咱們在antd-components.js文件中聲明一個antd-input
的自定義組件,而後在JSON表單中引入該自定義表單組件:
init.js
import Form from 'json_transform_form' import components from './antd-components' From.createCustomComp(components) const config = { formKey: 'paramAddFromAntd', data: { name: '', }, config: [ { type: 'antd-input', dataKey: 'name', label: 'Param', placeholder: '請輸入param', validate: ['required', /^[a-zA-Z_{}0-9]+$/g] } ] } <From ref={ref => this.FormWrap = ref} config={config}></From>
使用container來引入antd組件庫,其原理就是經過container將antd組件封裝成'antd-input'自定義組件,而後使用它,這種方式不只能夠用來封裝組件庫,還能夠用來共享一些共用表單組件,能夠將經常使用的複雜表單組件封裝在一個共用文件裏,而後在不一樣項目中引用,就能夠跨項目共用表單組件。
在自定義組件中,若是須要自定義表單提交數據函數,可是又不能重寫render方法以防覆蓋原先的render方法,因此可使用modifyDataFn方法來覆蓋render中的提交數據部分。
modifyDataFn: ({changeFn, changeDataFn}, {parent, self}) => { let {parentData} = parent parentData = parentData.map(item => ({ ...item, name: self.curData })) changeDataFn(parent.parentKey, parentData) }
在JSON表單JSON配置中,有assistData的選填字段,該字段爲JSON表單處理控制邏輯的額外數據,例如在表單內有一個刷新按鈕,其實現代碼以下:
{ data: {}, assistData: { refreshParam: false }, config: [ { type: 'container', dataKey: 'assistData.refreshParam', render: (curData, config, {changeFn, changeDataFn}) => { const handleClick = () => { changeDataFn('assistData.refreshParam' ,true) setTimeout(() => { changeDataFn('assistData.refreshParam' ,false) }, 1000 * 3) } return <React.Fragment> { config.index === config.parentData.length - 1 && <Popover placement="top" content="刷新param列表"> <Button shape="circle" loading={curData} onClick={handleClick}>{!curData && <Icon type="reload" />}</Button> </Popover> } </React.Fragment> } }, ] }
注意: 若是要使用assistData中的數據,其dataKey必須以assistData
開頭,且必須使用changeDataFn
自定義提交assistData數據。
{ type: 'container', dataKey: 'param', render: (curData, config, {changeFn, changeDataFn, JSONForm}) => { return <div> { JSONForm([ { type: 'input', dataKey: 'name', placeholder: '請輸入param', validate: ['required'], } ]) } </div> }
這樣就能夠在container內嵌套組件配置,實現更復雜的表單組件。
非實時表單提交數據,就是在表單輸入完畢後,點擊提交按鈕統一提交全部的數據,其提交的方式以下:
function handleClick() { this.FormRefs.getValue((valid, data) => { // valid 表示校驗結果,false表示校驗不經過 }) }
實時表單的提交首先須要註冊提交函數:
componentDidMount(){ this.FormRefs.registerSubmit((valid, data) => { console.log(valid, data) }) }
接着在配置裏設置容許實時提交的字段:
{ formKey: '', realTimeSubmit: true }
若是須要在某些表單組件裏自定義是否實時提交,須要在組件配置裏設置阻止實時提交字段爲true:
{ dataKey: '', preventSubmit: true }
a. 按複雜度分類 1. 簡單表單:表單組件爲input、select、textarea等常見的幾種,且表單組件之間邏輯獨立 2. 複雜表單:表單組件內容和交互複雜且相互之間存在複雜的邏輯 其中複雜表單又能夠分爲: 1. 聯動表單,上一個表單組件會影響接下來表單的值 2. 實時表單,表單組件的事件會觸發表單的實時提交,例如篩選表單 3. 富控制表單,表單內部含有不少的控制邏輯
JSON表單最適合的應用場景是簡單表單,它能夠用極少的代碼,快速的構建出表單來,對於複雜類型的表單,JSON表單須要使用container來構建複雜的表單組件、處理複雜的控制邏輯,其代碼量優點雖然並不明顯,可是JSON表單可使其代碼清晰,將表單組件和表單邏輯完全解耦,便於抽離和維護,便於共享經常使用組件,也帶來很多的好處。
到目前爲止,JSON表單適合大部分的表單應用場景。
在個人項目,我嘗試了使用原始表單和JSON表單兩種方式來實現同一個表單頁,原始表單我編寫了600多行的代碼,而在JSON表單中,只有不到150行。