美文网首页让前端飞前端Vue专辑Java 核心技术
利用Vue构造器创建Form组件的通用解决方法

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

作者: a333661d6d6e | 来源:发表于2018-12-07 21:38 被阅读5次

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

    在应用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中,当用户触发时,在页面中挂载,关闭时自动注销。

    实例

    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`)
      })//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860
     }
     }
    }
    </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()
      }//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860
     },
     destroyElement () {
      this.$destroy()
     },
     close () {
      this.isVisible = false
     }
     },
      
     mounted () {
     document.body.appendChild(this.$el)
     },
      
     destroyed () {
     this.$el.parentNode.removeChild(this.$el)
     }//欢迎加入前端全栈开发交流圈一起学习交流:864305860
    }
    </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
     }//欢迎加入前端全栈开发交流圈一起学习交流:864305860
    }
    

    首先利用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()
     } //欢迎加入前端全栈开发交流圈一起学习交流:864305860
    },
    destroyed () {
     this.$el.parentNode.removeChild(this.$el)
    }
    

    组件注销的时间与它在页面中显示息息相关,当<form />在页面中不可见时候,需要注销它

    method: {
     destroyElement () {
     this.$destroy()
     } //欢迎加入前端全栈开发交流圈一起学习交流:864305860
    },
    destroyed () {
     this.$el.parentNode.removeChild(this.$el)
    }
    

    一般Form组件有两个功能:

    • done:代表用户确认;
    • cancel:代表用户取消;

    当done或cancel触发时,APP组件内可能会有相应的变化,所以在组件实例化之后,利用$on去监听对应的done事件以及cancel事件。

    done && inlineListen({
     method: 'done',
     options,
     instance
    })//欢迎加入前端全栈开发交流圈一起吹水聊天学习交流:864305860
    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))
     })//欢迎加入前端全栈开发交流圈一起学习交流:864305860
     }
    }//帮助突破技术瓶颈,提升思维能力
    

    使用

    可以将上述属于<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
     }//欢迎加入前端全栈开发交流圈一起学习交流:864305860
     }//面向1-3年前端人员
    }//帮助突破技术瓶颈,提升思维能力
    

    再通过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)
      }//欢迎加入前端全栈开发交流圈一起学习交流:864305860
      }
     }//面向1-3年前端人员
     }
    }//帮助突破技术瓶颈,提升思维能力
    </script>
    

    调用时,只需

    export default {
     name: 'App',
     methods: {
     register: transform(register),
     
     handleClick () {
      this.register({
      propsData: {
       ...
      },
      // done: data => function
      done () {
       // 外部关闭
       this.close()
      }//欢迎加入前端全栈开发交流圈一起学习交流:864305860
      })
     }//面向1-3年前端人员
     }
    }//帮助突破技术瓶颈,提升思维能力
    

    PS:如果业务场景需要,在外部控制表单的关闭时,只需要改变done function的context,也就是this指针指向<Form/>。

    总结

    通过上述的transform函数,将原有的注入式组件转化为了命令式,简化了页面状态的维护,在通过mixins混入公有data以及method,简化了表单组件开发。上述方法也可用于开发toast、alert、confirm等组件,只需要将

    Vue.prototype.method = transform(Toast-Component)
    

    结语

    感谢您的观看,如有不足之处,欢迎批评指正。

    本次给大家推荐一个免费的学习群,里面概括移动应用网站开发,css,html,webpack,vue node angular以及面试资源等。
    对web开发技术感兴趣的同学,欢迎加入Q群:864305860,不管你是小白还是大牛我都欢迎,还有大牛整理的一套高效率学习路线和教程与您免费分享,同时每天更新视频资料。
    最后,祝大家早日学有所成,拿到满意offer,快速升职加薪,走上人生巅峰。

    相关文章

      网友评论

        本文标题:利用Vue构造器创建Form组件的通用解决方法

        本文链接:https://www.haomeiwen.com/subject/afsgcqtx.html