利用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 相关文章推荐
鼠标右击事件代码(asp.net后台)
Jan 27 Javascript
js正文内容高亮效果的实现方法
Jun 30 Javascript
Java Mybatis框架入门基础教程
Sep 21 Javascript
JavaScript事件类型中焦点、鼠标和滚轮事件详解
Jan 25 Javascript
详解Vue监听数据变化原理
Mar 08 Javascript
jQuery实现选中行变色效果(实例讲解)
Jul 06 jQuery
Bootstrap弹出框之自定义悬停框标题、内容和样式示例代码
Jul 11 Javascript
Vue 自定义动态组件实例详解
Mar 28 Javascript
详解Vue2.0配置mint-ui踩过的那些坑
Apr 23 Javascript
vue多次循环操作示例
Feb 08 Javascript
基于原生js实现九宫格算法代码实例
Jul 03 Javascript
JS页面动态绘图工具SVG,Canvas,VML介简介
Oct 16 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
我的论坛源代码(八)
2006/10/09 PHP
PHP获取表单textarea数据中的换行问题
2010/09/10 PHP
基于PHP实现的事件机制实例分析
2015/06/18 PHP
php使用curl并发减少后端访问时间的方法分析
2016/05/12 PHP
curl 出现错误的调试方法(必看)
2017/02/13 PHP
Yii2 queue的队列使用详解
2019/07/19 PHP
jQuery 源码分析笔记(3) Deferred机制
2011/06/19 Javascript
ie8本地图片上传预览示例代码
2014/01/12 Javascript
javascript简单性能问题及学习笔记
2014/02/04 Javascript
raphael.js绘制中国地图 地图绘制方法
2014/02/12 Javascript
javascript判断数组内是否重复的方法
2015/04/21 Javascript
JQuery中DOM加载与事件执行实例分析
2015/06/13 Javascript
javascript自定义in_array()函数实现方法
2015/08/03 Javascript
JS定时器实现数值从0到10来回变化
2016/12/09 Javascript
微信小程序 视图容器组件的详解及实例代码
2017/01/19 Javascript
微信小程序之滚动视图容器的实现方法
2017/09/26 Javascript
详解webpack require.ensure与require AMD的区别
2017/12/13 Javascript
微信小程序自定义组件封装及父子间组件传值的方法
2018/08/28 Javascript
vue根据进入的路由进行原路返回的方法
2018/09/26 Javascript
关于JS解构的5种有趣用法
2019/09/05 Javascript
如何基于js判断浏览器版本
2020/02/20 Javascript
使用graphics.py实现2048小游戏
2015/03/10 Python
探究Python的Tornado框架对子域名和泛域名的支持
2015/05/02 Python
python制作简单五子棋游戏
2019/06/18 Python
Python代码使用 Pyftpdlib实现FTP服务器功能
2019/07/22 Python
python日志模块logbook使用方法
2019/09/19 Python
Python填充任意颜色,不同算法时间差异分析说明
2020/05/16 Python
西班牙多品牌鞋店连锁店:Krack
2018/11/30 全球购物
为什么在使用动态 SQL 语句时必须为低层数据库对象授予权限
2012/12/13 面试题
博士生入学考试推荐信
2013/11/17 职场文书
室内设计专业个人的自我评价
2013/12/18 职场文书
投标担保书范文
2014/04/02 职场文书
校园活动策划方案
2014/06/13 职场文书
python opencv人脸识别考勤系统的完整源码
2021/04/26 Python
Opencv实现二维直方图的计算及绘制
2021/07/21 Python
js 实现验证码输入框示例详解
2022/09/23 Javascript