利用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 相关文章推荐
用JTrackBar实现的模拟苹果风格的滚动条
Aug 06 Javascript
一段效率很高的for循环语句使用方法
Aug 13 Javascript
Javascript变量函数浅析
Sep 02 Javascript
Jquery网页出现的乱码问题的三种解决方法
Jun 30 Javascript
Mac地址验证的javascript代码
Nov 09 Javascript
AspNet中使用JQuery boxy插件的确认框
May 20 Javascript
.NET微信公众号开发之创建自定义菜单
Jul 16 Javascript
js实现上一页下一页的效果【附代码】
Mar 10 Javascript
通过BootStrap实现轮播图的实际应用
Sep 26 Javascript
零基础轻松学JavaScript闭包
Dec 30 Javascript
JavaScript Date 知识浅析
Jan 29 Javascript
BootStrap Table前台和后台分页对JSON格式的要求
Jun 28 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
Adodb的十个实例(清晰版)
2006/12/31 PHP
判断PHP数组是否为空的代码
2011/09/08 PHP
PHP+MYSQL会员系统的开发实例教程
2014/08/23 PHP
PHP人民币金额转大写实例代码
2015/10/02 PHP
每个程序员都需要学习 JavaScript 的7个理由小结
2016/09/03 Javascript
JavaScript中的ajax功能的概念和示例详解
2016/10/17 Javascript
js eval函数使用,js对象和字符串互转实例
2017/03/06 Javascript
微信小程序实战之上拉(分页加载)效果(2)
2017/04/17 Javascript
Angular2使用Guard和Resolve进行验证和权限控制
2017/04/24 Javascript
AngularJS 实现点击按钮获取验证码功能实例代码
2017/07/13 Javascript
微信小程序删除处理详解
2017/08/16 Javascript
vue项目在安卓低版本机显示空白的原因分析(两种)
2018/09/04 Javascript
video.js 一个页面同时播放多个视频的实例代码
2018/11/27 Javascript
vue用BMap百度地图实现即时搜索功能
2019/09/26 Javascript
js中Function引用类型常见有用的方法和属性详解
2019/12/11 Javascript
vue 实现超长文本截取,悬浮框提示
2020/07/29 Javascript
JavaScript快速调试的两个技巧
2020/11/04 Javascript
详解Python中的多线程编程
2015/04/09 Python
python开发之函数定义实例分析
2015/11/12 Python
python 异常处理总结
2016/10/18 Python
Python爬虫之正则表达式基本用法实例分析
2018/08/08 Python
Python实现的逻辑回归算法示例【附测试csv文件下载】
2018/12/28 Python
Windows下python3安装tkinter的问题及解决方法
2020/01/06 Python
NumPy统计函数的实现方法
2020/01/21 Python
解决pycharm每次打开项目都需要配置解释器和安装库问题
2020/02/26 Python
python opencv 检测移动物体并截图保存实例
2020/03/10 Python
使用tensorflow框架在Colab上跑通猫狗识别代码
2020/04/26 Python
Python实现加密接口测试方法步骤详解
2020/06/05 Python
Python常用类型转换实现代码实例
2020/07/28 Python
Wedgwood美国官网:英国骨瓷,精美礼品及家居装饰
2018/02/17 全球购物
材料物理专业大学毕业生求职信
2013/10/15 职场文书
党的群众路线教育实践活动个人对照检查材料(医生)
2014/11/05 职场文书
调任通知
2015/04/21 职场文书
2016年“5.12”国际护士节活动总结
2016/04/06 职场文书
python数据可视化JupyterLab实用扩展程序Mito
2021/11/20 Python
详解MySQL的主键查询为什么这么快
2022/04/03 MySQL