利用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 相关文章推荐
简约JS日历控件 实例代码
Jul 12 Javascript
jQuery选择器querySelector的使用指南
Jan 23 Javascript
纯CSS3代码实现滑动开关效果
Aug 19 Javascript
详解JS正则replace的使用方法
Mar 06 Javascript
jQuery中Nicescroll滚动条插件的用法
Nov 10 Javascript
微信小程序 实例开发总结
Apr 26 Javascript
BootStrap中的Fontawesome 图标
May 25 Javascript
js实现加载页面就自动触发超链接的示例
Aug 31 Javascript
VUE.CLI4.0配置多页面入口的实现
Nov 25 Javascript
vue vantUI tab切换时 list组件不触发load事件的问题及解决方法
Feb 14 Javascript
JavaScript实现移动端弹窗后禁止滚动
May 25 Javascript
javascript实现打砖块小游戏(附完整源码)
Sep 18 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
PHP array 的加法操作代码
2010/07/24 PHP
PHP中$_FILES的使用方法及注意事项说明
2014/02/14 PHP
PHP实现绘制3D扇形统计图及图片缩放实例
2014/10/01 PHP
Javascript 原型和继承(Prototypes and Inheritance)
2009/04/01 Javascript
javascript 日期时间函数(经典+完善+实用)
2009/05/27 Javascript
html数组字符串拼接的最快方法
2009/09/16 Javascript
更换select下拉菜单背景样式的实现代码
2011/12/20 Javascript
JavaScript中的运算符种类及其规则介绍
2013/09/26 Javascript
jQuery事件绑定与解除绑定实现方法
2015/04/15 Javascript
正则表达式优化JSON字符串的技巧
2015/12/24 Javascript
小白谈谈对JS原型链的理解
2016/05/03 Javascript
深入理解jQuery中的事件冒泡
2016/05/24 Javascript
jQuery实现可移动选项的左右下拉列表示例
2016/12/26 Javascript
详解微信小程序 登录获取unionid
2017/06/27 Javascript
浅谈React Native 中组件的生命周期
2017/09/08 Javascript
浅谈关于iview表单验证的问题
2018/09/29 Javascript
nodejs 使用http进行post或get请求的实例(携带cookie)
2019/01/03 NodeJs
vue父组件触发事件改变子组件的值的方法实例详解
2019/05/07 Javascript
通过图带你深入了解vue的响应式原理
2019/06/21 Javascript
vue+elementUI中表格高亮或字体颜色改变操作
2020/11/02 Javascript
Vant 在vue-cli 4.x中按需加载操作
2020/11/05 Javascript
Python实现获取磁盘剩余空间的2种方法
2017/06/07 Python
python使用PyCharm进行远程开发和调试
2017/11/02 Python
python 通过xml获取测试节点和属性的实例
2018/03/31 Python
在pycharm中python切换解释器失败的解决方法
2018/10/29 Python
解决Mac下首次安装pycharm无project interpreter的问题
2018/10/29 Python
tensorflow的ckpt及pb模型持久化方式及转化详解
2020/02/12 Python
Jupyter Notebook 安装配置与使用详解
2021/01/06 Python
python爬虫scrapy基于CrawlSpider类的全站数据爬取示例解析
2021/02/20 Python
小学生家长评语集锦
2014/01/30 职场文书
小学语文国培感言
2014/03/04 职场文书
大学理论知识学习自我鉴定
2014/04/28 职场文书
应届毕业生自荐书
2014/06/18 职场文书
党员干部反四风民主生活会对照检查材料思想汇报
2014/10/12 职场文书
大学军训口号大全
2015/12/24 职场文书
Java Spring Lifecycle的使用
2022/05/06 Java/Android