在使用vue框架时,正常的组件调用都是直接使用模板或使用选择器渲染,但在项目中有遇到,直接在js中用api弹出对话框的业务(公共逻辑的一个js文件夹中,代码使用了一个其它库,js直接弹出对话框);使用了vue框架,为了弹窗不必再引入其他库,就需要了解到vue组件的api式调用。

  • api式调用,指的就是可以在一段js代码中,直接渲染组件,比如这里的例子弹窗

    1
    2
    3
    4
    5
    //xxx.js
    funciton doSomething(){
    dialog.open(params); //页面直接弹出对话框进行交互
    }
    //类似layer库的,layer.open(...)
  • 实现

    • 首先,vue组件的生命周期顺序是:created–》mounted,然后页面就会渲染组件。可以知道正常的vue组件渲染是,生成vue组件对象然后mounted挂载;那么我们需要的是js调用时才渲染也即才挂载,那么想要实现编程式调用,那么就要延后挂载,由我们决定什么时候再挂载

    • 类似vue普通组件的,我们得先构建好vue对象。但只有在需要被调用时才进行挂载;那么如何构建vue对象呢?

    • 利用Vue.extend()构建一个带挂载的vue对象,这里扩展了Vue类(Vue构造函数);理解extend,先理解两点:

      1. 返回的是一个构造函数(constructor)而不是一个对象,想要这种类型的对象还需要用new操作符;
      2. Vue.extend()接收一个参数,是一个组件的选项对象,extend做的操作大概就是像new Vue()一样——用这个组件的选项对象的去构造一个vue对象。但是和new构造不同点是extend返回的是一个经过这样初始化的构造函数而不是对象。也即,由这个构造函数构造出来的对象是肯定会经过之前那个组件的选项对象初始化的vue对象,有点类似延迟函数的方式,先进行了一层初始化。是Vue对象的一个“子类”。
      • 2中这样有什么用?如同extend这个词的原来意思,扩展,Vue.extend得到的构造函数构造的对象无疑是vue对象,但是这个对象比原生的vue多初始化了一些东西,也即扩展了也些东西。除此之外,返回了一个构造函数,意味着这个是独立于原生Vue构造函数的,对原生Vue没有任何影响(这一点是和mixin的本质区别,mixin就是影响原生Vue的)
      • 再理解官方概括:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
    • 得到构造函数后,使用new操作符即可构造vue组件对象,这时再暴露一个封装了使用vm.$mount()的函数,让使用者调用这个函数触发渲染组件即可完成编程式的调用

    • 例子:一个对话框(样式很丑。。)

    dialog.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    import Vue from 'vue'
    //import './dialog.css'

    export let Dialog = {}

    // 1.使用Vue.extend()构建一个Vue组件的构造函数
    const ApiComponent = Vue.extend({
    name: 'apiComponent',
    render: function (h) {
    console.log(h)
    return (
    <div class="cover" onclick={this.onClose}>
    <div class="dialog" >
    {this.msg}
    <button class="btn" onclick={this.onClose}>确定</button>
    </div>

    </div>
    )
    },
    data: function () {
    return {
    msg: 'i a api component'
    }
    },
    mounted(){
    document.querySelector('.dialog').addEventListener('click',(e)=>e.stopPropagation(),false)
    },
    methods: {
    setMsg: function (params) {
    this.msg = params
    },
    onClose:function () {
    // 触发销毁
    this.$destroy()
    },
    },
    destroyed(){
    //销毁
    this.$cancel(this.$el)
    }
    })

    // 2.封装打开对话框方法(使用vm.$mount())
    Dialog.open = function (){
    //创建挂载点
    const ref = document.createElement('div')
    document.body.appendChild(ref)

    //构造vue对象,因为Vue.extend()返回的是一个Vue构造函数的“子类”
    //也具有Vue构造函数函数的功能,所以也是能接收一个vue组件的选项对象
    //构造时如果当前的与Vue.extend()的某些属性重复,优先使用当前的
    let api = new ApiComponent({
    data:function(){
    return {
    msg:'haha'
    }
    }
    })

    //调用$mount挂载
    api.$mount(ref)
    return api.$el
    }

    // 3.这里封装一个销毁/关闭方法
    const close = (ref)=>{
    ref && ref.remove()
    }
    ApiComponent.prototype.$cancel = close

    DemoComponent.Vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <template>
    <div>
    <h1>demo-dialog</h1>
    <button @click="onOpen">open</button>
    </div>
    </template>

    <script>
    import {Dialog} from './dialog.js'

    export default {
    name:'demo-dialog',
    methods:{
    onOpen:function(){
    //直接调用,页面应该会直接弹窗
    Dialog.open()
    }
    }
    }
    </script>

    <style>
    /*这里的样式应该直接导入到dialog.js*/
    .dialog {
    position: absolute;
    top: 20%;
    width: 50%;
    height: 50%;
    margin: 0 25%;
    background-color: white;
    }
    .btn {
    position: absolute;
    bottom: 0;
    width: 5rem;
    height: 2rem;
    margin: 1rem;
    background-color: white;
    }
    .cover {
    position: fixed;
    top: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(128, 128, 128, 0.315);
    }
    </style>

参考:

Vue-extend

Vue-mixin

vm-destroy