樹醬但願將前端的樂趣帶給你們 本文已收錄 github.com/littleTreem… 喜歡就star✨html
前沿:中後臺應用中表單需求頗多,左手一個表單,右手又是一個表單,無窮無盡,若是用模版一個個來寫,不單寫起來費時費力,並且看起來也是天花亂墜,因而這個時候你會去設想,那有沒有什麼方式能夠去替換瑣碎的手寫表單模版的方式呢?讓表單是「配出來」的,而不是擼出來的,讓你輕鬆解決 form 表單,也再也不爲表單而煩惱。答案就是:動態表單前端
一個表單須要什麼?無疑是包含了form數據的收集、驗證及提交等等功能,讓咱們看看下面這個基於iview組件庫的form表單vue
這個簡單的表單,若是咱們用手寫模版的方式擼出來,模版部分就是以下所示👇git
數據初始化定義和驗證提交邏輯以下 github
以上就完成一個具有數據收集、驗證、提交、重製的表單,可是相對應問題也來了,這裏用模板並非最好的選擇,代碼過於冗長,也存在重複代碼,若是個人項目中十幾個表單甚至更多,我豈不是都要去寫怎麼多代碼去維護這類表單,會不會顯得太冗餘,接下來進入咱們今天的主角:動態表單,讓咱們看看怎麼讓他「動」💃起來算法
我指望的表單是能夠配出來的,經過JSON來動態渲染生成相應的表單,表單中涉及的組件(好比Input、Select)能夠經過獲取JSON的配置所需的去渲染,上一小節提到的模版渲染顯然就不適用此次場景了,雖然vue官方推薦在絕大多數狀況下使用模板來建立你的temlate,可是一些場景仍是須要用到渲染函數render 官方文檔點我👈json
咱們先看看這個例子,Vue.js 的 mount 函數,將h()生成的VNode節點函數,渲染成真實 DOM 節點,並掛載到根節點上前端工程化
這個h()
函數本質上是createElement 函數,這個函數的做用就是生成一個 VNode節點(虛擬節點),它不是一個實際的 DOM 元素。叫createNodeDescription(建立節點描述),咱們是經過它所包含的信息會來告訴 Vue 頁面上須要渲染什麼樣的節點,再經過diff算法能夠追蹤dom的變化api
拓展:你可能會好奇爲啥是叫h()函數
,而不是createElement()
的簡稱c()
數組
h出自hyperscript首字母,最原始的定義是「Create HyperText with JavaScript」,而HyperText則是出自咱們熟悉的則HTML 是 hyper-text markup language 的縮寫(超文本標記語言),因此能夠理解爲Hyperscript是指生成HTML的 script 腳本
createElment函數接受三個參數,分別是:
下面用一個簡單例子說明渲染函數的使用👇
上面例子的模版渲染和渲染函數渲染的結果是同樣的,固然模板本質上也是經過 Compile 編譯 獲得 渲染函數
render()
,因此說其實渲染函數更高效,更快,減小了編譯的時間,關於編譯能夠看這篇vue 編譯過程,由templete編譯成render函數
渲染函數render與模板template的區別
扯完渲染函數,接下來介紹下動態表單的思路
這裏使用的是iview組件庫的基礎上實現的動態表單,建立的組件都是基於iview來實現的,下面是具體的流程圖
我用第一節的例子來配置一個JSON格式的表單配置(由於配置文件過長,改用文字)
const formOption = {
ref: 'formValidate',
style: { //表單樣式,非必須
width: '300px',
margin: 'auto',
},
className: 'form',
formProps: { //非必須
'label-width': 80,
},
formData: {//所要監聽的表單字段數據,必須
name: '',
city: '',
sex: 'male',
},
formItem: [ //iview form表單的每一個formItem,必須
{
type: 'input',
label: '名稱', //對應formItem的label
key: 'name', //key對應formData中的字段
props: {
placeholder: '請輸入名稱',
},
rules: { //表單檢測規則,非必須
required: true,
message: '請填寫名稱',
trigger: 'blur',
},
},
{
type: 'select',
label: '城市', //對應formItem的label
key: 'city', //key對應formData中的字段
props: {
placeholder: '請輸入名稱',
},
children: [{ label: 'xml', value: '1' },
{ label: 'json', value: '2' },
{ label: 'hl7', value: '3' }
],
rules: { //表單檢測規則,非必須
required: true,
message: '請選擇城市',
trigger: 'blur',
},
},
{
type: 'radioGroup',
key: 'type',
label: 'sex',
children: [
{
text: 'female',
label: 'female',
},
{
text: 'male',
label: 'male',
},
],
events: {
'on-change': (vm, value) => {
vm.$emit('on-change', value);
},
},
}
],
events: events('formValidate'),//表單按鈕組
}
複製代碼
還有相應的事件按鈕統一在events中處理(可複用)
第一節例子涉及到表單組件分別是Input、Select、radioGroup、formItem。分別是定義它們的render函數
集合iview組件庫Input的API,包括props屬性、events事件、slot插槽、methods方法等來定義渲染函數,具體實現以下圖所示
function generateInputComponent(h, formData = {}, obj, vm) {
const key = obj.key? obj.key : ''
let children = []
if (obj.children) { //input有子集,走這裏
children = obj.children.map(item => {
let component
if (item.type == 'span') { //複合型輸入框狀況
component = h('span', {
slot: item.slot
}, [item.text])
} else {
let func = componentObj[item.type]
component = func? func.call(vm, h, formData, item, vm) : null
}
return component
})
}
return h('Input', {
props: {
value: key? formData[key] : '',
...obj.props
},
style: obj.style,
on: {
...translateEvents(obj.events, vm), //時間綁定
input(val) {
if (key) {
formData[key] = val
}
}
},
slot: obj.slot
}, children)
}
//事件bind
function translateEvents(events = {}, vm, formData = {}) {
const result = {}
for (let event in events) {
result[event] = events[event].bind(vm, vm, formData);
}
return result
}
複製代碼
function generateSelectComponent(h, formData = {}, obj, vm) {
const key = obj.key? obj.key : ''
let components = []
if (obj.children) {
components = obj.children.map(item => {
if (item.type == 'optionGroup') {
return h('OptionGroup', {
props: item.props? item.props : item
}, item.children.map(child => {
return h('Option', {
props: child.props? child.props : child
})
}))
} else {
return h('Option', {
props: item.props? item.props : item
})
}
})
}
return h('Select', {
props: {
value: formData[key],
...obj.props
},
style: obj.style,
on: {
...translateEvents(obj.events, vm),
input(val) {
if (key) {
formData[key] = val
}
}
},
slot: obj.slot
}, components)
}
複製代碼
這裏只是展現部分組件的實現方式,主要目的是梳理開發及應用的流程思路
function generateEventsComponent(h, formData = {}, obj, vm) {
const components = [];
if(obj.submit) {
const submit = h('Button', {
props: obj.submit.props,
style: obj.submit.style,
class: obj.submit.className,
on: {
click() {
//提交前校驗
vm.$refs[obj.ref].validate((valid) => {
if (valid) {
obj.submit.success.call(vm, formData, vm)
} else {
obj.submit.fail.call(vm, formData, vm)
}
})
}
}
}, [obj.submit.text])
components.push(submit)
}
if (obj.reset) {
const reset = h('Button', {
props: obj.reset.props,
style: {
...obj.reset.style,
},
class: obj.reset.className,
on: {
click() {
vm.$refs[obj.ref].resetFields() //重置表單
obj.reset.success.call(vm, formData, vm);
}
}
}, [obj.reset.text])
components.push(reset)
}
return h('div',{
class: 'vue-events',
style: {
...obj.style
}
}, components)
}
複製代碼
實現好組件的動態生成邏輯,這個時候須要一個入口(formBuild.js),就是根據配置去映射相應的組件並生成合並,組合成爲最終要的表單
// form-build.js
import componentObj from './utils'
export default {
props: {
options: {
type: Object,
required: true
},
},
render(h) {
const options = this.options
const formData = options.formData
if (!options.formItem) {
return h('div')
}
const components = options.formItem.map(item => {
let func = componentObj[item.type]
let subComponent = func? func.call(this, h, formData, item, this) : null
let component = componentObj.formItem(h, item, subComponent, formData)
return componentObj.col(h, item, component)
})
const childComp = [];
const fromComp = h('Form', {
ref: options.ref,
style: options.style ? options.style : '',
props: {
model: formData,
...options.formProps
},
class: 'vue-generate-form'
}, [
h('Row', {
props: options.rowProps
}, components)
]);
childComp.push(fromComp);
if (options.events) {
const eventComo = componentObj.events(h, formData, obj.events , vm)
childComp.push(eventComp)
}
return h('div', [childComp]);
}
}
複製代碼
還須要定義vue的插件安裝
on-click
這樣的事件。可使用 DOM 元素原生事件代替,例如 click
以上就能夠經過render渲染函數來完成動態表單工具的實現,本文主要是經過一種思路去介紹整個開發,動態表單有多種實現方式,固然你可能也有疑惑
你能夠參考下開源的form-create(支持3種 UI 框架:Iview、ElementUI、Ant-design-vue)是如何實現的 form-create工具庫
可視化表單設計工具也很香,有興趣的童鞋能夠了解 vue-ele-form-generator
文章思路來源:vue-form-make
往期文章