利用Vue构造器创建Form组件的通用解决方法


Posted in Javascript onDecember 03, 2018

前言

在前端平常的业务中,无论是官网、展示页还是后台运营系统都离不开表单,它承载了大部分的数据采集工作。所以如何更好地实现它,是平常工作中的一个重要问题。

在应用Vue框架去开发业务时,会将页面上每个独立的可视/可交互区域拆分为一个组件,再通过多个组件的自由组合来组成新的页面。例如

<template>
 <header></header>
 ...
 <content></content>
 ...
 <footer></footer>
</template>

当用户的某个行为触发表单时(例如注册、建立内容等),期望在页面中弹出一个From组件。通常的做法是在template中填入一个<form>组件用于开发,并通过控制data中的UI.isOpen来对其display进行控制,例如在当前<template>组件内开发

<template>
 <header></header>
 ...
 <content></content>
 ...
 <footer></footer>
 ...
 <register-form v-if="UI.isOpen">
 <form-item></form-item>
 ...
 <submit-button></submit-button>
 </register-form>
</template>

这样开发有一点优势,Form组件与其父组件之间可以通过prop以及$emit方便通信。但是也会有以下几个缺陷:

  • 当前组件的data必须要有UI.isOpen来控制表单,如果存在多个表单时,就会有大量的状态来维护表单的开关;
  • 如果表单多次弹出时,可能需要对表单的data进行重置;
  • 与组件化思想相违背,表单不属于当前页面,它只是由于用户行为触发的结果。

为了解决以上缺陷,并且还能具备方便通信的优势,本文选择用Vue.extend将原有<form>组件转化为method function,并维护在当前组件的method中,当用户触发时,在页面中挂载,关闭时自动注销。

实例

演示地址:演示实例

代码地址:FatGe github (本地下载)

APP组件

<template>
 <div id="app">
 <el-button 
  type="primary" icon="el-icon-edit-outline"
  @click="handleClick"
 >注册</el-button>
 </div>
</template>

<script>
import register from './components/register'
import { transform } from './transform'

export default {
 name: 'App',
 methods: {
 register: transform(register),

 handleClick () {
  this.register({
  propsData: { name: '皮鞋' },
  done: name => alert(`${name}牛B`)
  })
 }
 }
}
</script>

当<el-button>的点击事件触发时,调用register方法,将表单组件挂载在页面中。

Form组件

<template>
 <div class="mock" v-if="isVisible">
 <div class="form-wrapper">
  <i class="el-icon-close close-btn" @click.stop="close"></i>

  ...<header />
  ...<content />

  <div class="footer">
   <el-button 
    type="primary"
    @click="handleClick"
   >确定</el-button>

   <el-button 
    type="primary"
    @click="handleClick"
   >取消</el-button>
  </div>
 </div>
 </div>
</template>

<script>
export default {
 porps: { ... },

 data () {
 return {
  isVisible: true
 }
 },
 
 watch: {
 isVisible (newValue) {
  if (!newValue) {
  this.destroyElement()
  }
 }
 },
 
 methods: {
 handleClick ({ type }) {
  const handler = {
  close: () => this.close()
  }
 },
 destroyElement () {
  this.$destroy()
 },
 close () {
  this.isVisible = false
 }
 },
 
 mounted () {
 document.body.appendChild(this.$el)
 },
 
 destroyed () {
 this.$el.parentNode.removeChild(this.$el)
 }
}
</script>

在APP组件内并未维护<form>组件的状态,其打开或关闭只维护在自身的data中。

原理

上述代码中,最为关键的一步就是transform函数,它将原有的`从single-file components转化为了method function,其原理如下

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
 }
}

首先利用Vue.extend(options)创建一个<Form/>组件的子类

const _constructor = Vue.extend(component)

然后return function,它的功能是:

  • 将<form />组件转化为method
  • 在method调用时,将组件实例化并传递propsData
const {
 propsData
} = options
let 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 />在页面中不可见时候,需要注销它

method: {
 destroyElement () {
 this.$destroy()
 } 
},
destroyed () {
 this.$el.parentNode.removeChild(this.$el)
}

一般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混入。

<script>
import popupWin from '../mixins/popup-win'

export default {
 mixins: [popupWin],

 data () {
 return {
  input: '',
  gender: 1
 }
 },
 methods: {
 handleClick ({ type }) {
  const handler = {
  close: () => this.close(),
  confirm: () => {
   const { input } = this
   this.$emit('done', input)
  }
  }
 }
 }
}
</script>

调用时,只需

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)

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
jquery制作居中遮罩层效果分享
Feb 21 Javascript
原生js仿jq判断当前浏览器是否为ie,精确到ie6~8
Aug 30 Javascript
jquery图片切换插件
Mar 16 Javascript
深入理解JS正则表达式---分组
Jul 18 Javascript
jQuery查找节点方法完整实例
Sep 13 Javascript
第一次接触神奇的Bootstrap
Oct 14 Javascript
Bootstrap CSS组件之分页(pagination)和翻页(pager)
Dec 17 Javascript
微信小程序实战之仿android fragment可滑动底部导航栏(4)
Apr 16 Javascript
JS获取短信验证码倒计时的实现代码
May 22 Javascript
JS实现HTML页面中动态显示当前时间完整示例
Jul 30 Javascript
小程序登录态管理的方法示例
Nov 13 Javascript
在vue中实现某一些路由页面隐藏导航栏的功能操作
Sep 21 Javascript
swiper.js插件实现pc端文本上下滑动功能示例
Dec 03 #Javascript
微信小程序显示倒计时功能示例【测试可用】
Dec 03 #Javascript
从零开始实现Vue简单的Toast插件
Dec 03 #Javascript
使用NestJS开发Node.js应用的方法
Dec 03 #Javascript
写gulp遇到的ES6问题详解
Dec 03 #Javascript
使用mpvue搭建一个初始小程序及项目配置方法
Dec 03 #Javascript
JS基于Location实现访问Url、重定向及刷新页面的方法分析
Dec 03 #Javascript
You might like
千呼万唤始出来,DOTA2勇士令状不朽宝藏Ⅱ现已推出
2020/08/25 DOTA
php英文单词统计器
2016/06/23 PHP
php编译安装php-amq扩展简明教程
2016/06/25 PHP
PHP中for循环与foreach的区别
2017/03/06 PHP
jquery获取被勾选的checked(选中)的那一行的3列和4列的值
2013/07/04 Javascript
对于this和$(this)的个人理解
2013/09/08 Javascript
html文档中的location对象属性理解及常见的用法
2014/08/13 Javascript
jQuery在线选座位插件seat-charts特效代码分享
2015/08/27 Javascript
原生JavaScript实现异步多文件上传
2015/12/02 Javascript
jQuery增加和删除表格项目及实现表格项目排序的方法
2016/05/30 Javascript
实例讲解JavaScript中instanceof运算符的用法
2016/06/08 Javascript
js从数组中删除指定值(不是指定位置)的元素实现代码
2016/09/13 Javascript
Bootstrap CSS使用方法
2016/12/23 Javascript
Vue 嵌套路由使用总结(推荐)
2020/01/13 Javascript
JavaScript享元模式原理与用法实例详解
2020/03/09 Javascript
JavaScript实现动态生成表格
2020/08/02 Javascript
Python实现ssh批量登录并执行命令
2016/10/25 Python
Python3 操作符重载方法示例
2017/11/23 Python
python读取csv文件并把文件放入一个list中的实例讲解
2018/04/27 Python
django模板结构优化的方法
2019/02/28 Python
PyCharm安装PyQt5及其工具(Qt Designer、PyUIC、PyRcc)的步骤详解
2020/11/02 Python
jupyter使用自动补全和切换默认浏览器的方法
2020/11/18 Python
css3 flex布局 justify-content:space-between 最后一行左对齐
2020/01/02 HTML / CSS
教你使用Canvas处理图片的方法
2017/11/28 HTML / CSS
草莓网官网:StrawberryNET
2019/08/21 全球购物
师范应届毕业生自荐信
2013/11/18 职场文书
我爱我家教学反思
2014/05/01 职场文书
正风肃纪查摆剖析材料
2014/10/10 职场文书
工会2014法制宣传日活动总结
2014/11/01 职场文书
政协委员个人总结
2015/03/03 职场文书
音乐会主持人开场白
2015/05/28 职场文书
美丽的大脚观后感
2015/06/03 职场文书
金砖之国观后感
2015/06/11 职场文书
护士工作心得体会
2016/01/25 职场文书
PyMongo 查询数据的实现
2021/06/28 Python
为什么MySQL8新特性会修改自增主键属性
2022/04/18 MySQL