树酱但愿将前端的乐趣带给你们 本文已收录 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
往期文章