在前端平常的业务中,无论是官网、展示页还是后台运营系统都离不开表单,它承载了大部分的数据采集工作。所以如何更好地实现它,是平常工作中的一个重要问题。
在应用Vue框架去开发业务时,会将页面上每个独立的可视/可交互区域拆分为一个组件,再通过多个组件的自由组合来组成新的页面。例如
... ... 复制代码
当用户的某个行为触发表单时(例如注册、建立内容等),期望在页面中弹出一个From
组件。通常的做法是在template
中填入一个<form>
组件用于开发,并通过控制data
中的UI.isOpen
来对其display
进行控制,例如在当前<template>
组件内开发<register-form>
。
... ... ... 复制代码 ...
这样开发有一点优势,Form
组件与其父组件之间可以通过prop
以及$emit
方便通信。但是也会有以下几个缺陷:
- 当前组件的
data
必须要有UI.isOpen
来控制表单,如果存在多个表单时,就会有大量的状态来维护表单的开关; - 如果表单多次弹出时,可能需要对表单的
data
进行重置; - 与组件化思想相违背,表单不属于当前页面,它只是由于用户行为触发的结果。
为了解决以上缺陷,并且还能具备方便通信的优势,本文选择用Vue.extend
将原有<form>
组件转化为method function
,并维护在当前组件的method
中,当用户触发时,在页面中挂载,关闭时自动注销。
实例
演示地址:
代码地址:
- APP组件
复制代码注册
当<el-button>
的点击事件触发时,调用register
方法,将表单组件挂载在页面中。
- Form组件
复制代码......
在APP组件内并未维护<form>
组件的状态,其打开或关闭只维护在自身的data
中。
原理
上述代码中,最为关键的一步就是transform
函数,它将原有的`
const transform = (component) => { const _constructor = Vue.extend(component) return function (options = {}) { const { propsData } = options let instance = new _constructor({ propsData }).$mount(document.createElement('div')) return instance }}复制代码
首先利用创建一个<Form/>
组件的子类
const _constructor = Vue.extend(component)复制代码
然后return function
,它的功能是:
- 将
<form />
组件转化为method
- 在
method
调用时,将组件实例化并传递propsData
const { propsData} = optionslet instance = new _constructor({ propsData}).$mount(document.createElement('div'))复制代码
为了能够控制实例化后的组件,选择instance
返回。
当组件实例化时,它只是挂载到document.createElement('div')
上,但是并没有挂载到页面上,所以需要将其appendChild
到页面中。为了更好的语义化,选择在组件的生命周期中完成它在页面中的挂载。实例化时,会触发组件mounted
生命周期,所以当其触发时可以挂载在document.body
中,具体如下
mounted () { document.body.appendChild(this.$el)}复制代码
有了挂载,就必须要有注销。对应的生命周期应该是destroyed
,所以
method: { destroyElement () { this.$destroy() } },destroyed () { this.$el.parentNode.removeChild(this.$el)}复制代码
组件注销的时间与它在页面中显示息息相关,当<form />
在页面中不可见时候,需要注销它
watch: { isVisible (newValue) { if (!newValue) { this.destroyElement() // 如果需要添加过渡动画 // this.$el.addEventListener('transitionend', this.destroyElement) } }}复制代码
一般Form组件有两个功能:
- done:代表用户确认;
- cancel:代表用户取消;
当done或cancel触发时,APP组件内可能会有相应的变化,所以在组件实例化之后,利用$on
去监听对应的done
事件以及cancel
事件。
done && inlineListen({ method: 'done', options, instance})cancel && inlineListen({ method: 'cancel', options, instance})复制代码
其中inlineListen
函数可以方便后续添加其他的event
,其代码为
const inlineListen = ({ method, options, instance}) => { let listener = `on${method}` instance[listener] = options[method] instance.$on(method, function (data) { this[listener](data) })}复制代码
也可以将上述方案封装成Promise
形式,如下
export const transform = (component) => { const _constructor = Vue.extend(component) return function (options = {}) { const { propsData } = options return new Promise((resolve, reject) => { let instance = new _constructor({ propsData }).$mount(document.createElement('div')) instance.$on('done', data => resolve(data)) }) }}复制代码
使用
可以将上述属于<Form/>
公有的data
以及method
独立出来,再通过mixins
引入到每个表单内,例如
export default { data() { return { visible: true } }, watch: { visible(newValue) { if (!newValue) { this.destroyElement() } } }, mounted() { document.body.appendChild(this.$el) }, destroyed() { this.$el.parentNode.removeChild(this.$el) }, methods: { destroyElement() { this.$destroy() }, close() { this.visible = false } }}复制代码
再通过mixins
混入。
复制代码
调用时,只需
export default { name: 'App', methods: { register: transform(register), handleClick () { this.register({ propsData: { ... }, // done: data => function done () { // 外部关闭 this.close() } }) } }}复制代码
PS:如果业务场景需要,在外部控制表单的关闭时,只需要改变done function
的context
,也就是this
指针指向<Form/>
。
总结
通过上述的transform
函数,将原有的注入式组件转化为了命令式,简化了页面状态的维护,在通过mixins
混入公有data
以及method
,简化了表单组件开发。上述方法也可用于开发toast、alert、confirm等组件,只需要将
Vue.prototype.method = transform(Toast-Component) 复制代码
原创声明: 该文章为原创文章,转载请注明出处。