Vue动态创建组件并插入到页面中

  • Post author:
  • Post category:vue


动态创建组件依靠

Vue.extend


案例分析


用户通过点击新增按钮,新加下一个页签,要实现这个需求只能通过动态的创建组件,然后添加到页面中,下面来实现这个需求(页签内容部分就不做了,重点放在动态创建页签按钮)

案例实现

<div id="app" class="app">
  <div class="title">XXX页面</div>
  <div id="tabBox" class="tabBox"></div>
  <div class="add" @click="add">+</div>
</div>

<template id="tab">
  <div class="tab">{{tabname}}</div>
</template>

<script src="./lib/vue.js"></script>
<script>
  const tab = {
    template: '#tab',
    props: ['tabname']
  }
  const vm = new Vue({
    data: {
      tabName: '',
      base: '页签',
      num: 1
    },
    methods: {
      add() {
        this.tabName = this.base + this.num
        this.num++
        const tabCmp = new (Vue.extend(tab))({propsData: {tabname: this.tabName}}).$mount()
        document.getElementById('tabBox').appendChild(tabCmp.$el)
      }
    }
  }).$mount('#app')
</script>


代码分析

  1. 创建一个vue页面,页面内包含3个

    div



    title

    是页面的标题,

    tabBox

    用来放所有的页签,

    add

    监听用户的点击添加页签;
  2. 局部注册一个组件

    tab

    ,传入了

    template



    props

    两项属性;
  3. 在父组件的

    methods

    中添加add事件处理函数,重点就在这个事件处理函数中,首先生成

    tabName

    ,然后利用

    Vue.extend

    一番操作得到组件

    tabCmp

    ,最后将创建的组件

    tabCmp

    插入到

    tabBox

    这个元素中;
  4. 当目前为止,当用户每次点击

    add

    按钮,便会在

    tabBox

    中插入一个

    tab

    组件,页面上也会做更新;
  5. 在这一连串的操作中核心就是

    Vue.extend

    ,下面具体认识以下这个API。

关于Vue.extend

先看Vue官网对于这个API的解释:

使用基础

Vue

构造器,创建一个“子类”。参数是一个包含组件选项的对象。


data

选项是特例,需要注意 – 在

Vue.extend()

中它必须是函数

  • 通俗的讲

    Vue.extend

    就是一个构造函数,它可以生成一个Vue实例(子类),可以像使用Vue构造函数一样使用他。特别注意的是他里面的data一定要是函数,这一点很好理解隔离作用域;
  • 将前面关键代码拆分一下理解,如下:
const tabCmp = new Vue.extend({
  template: "#tab",
  props: ['tabname']
}).$mount()
  • 这样看就他跟

    Vue

    构造函数没区别,

    $mount()

    负责挂载,不是这里的重点,不赘述;
  • 但这还不够,这还不能完成上面的动态创建组件,

    props

    的值怎么传到

    tabCmp

    组件中,这里可以看一下Vue源码中关于

    Vue. extend

    的实现

Vue.extend的源码实现

下面代码片段是我从源码中摘取出来的

// 取自vue-js/src/core/global-api/extend.js
Vue.extend = function (extendOptions: Object): Function {
  // ...
  const Super = this
  // ...
  const Sub = function VueComponent (options) {
    this._init(options)
  }
  // ...
  Sub['super'] = Super
  // ...
  return Sub
}

// 取自vue-js/src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {
  // ...
  if (options && options._isComponent) {
    // ...
  } else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
      )
    }
  // ...
}
  • 跟着源码走一遍

    new Vue.extend()

    得到一个

    Sub

    , 调用

    Sub

    会执行的

    _init

    方法,

    _init

    方法接受一个options对象,在这里只传入

    propsData

    就相当于在标签中绑定了一个props值,继续往下代码会执行

    mergeOptions()

    ,在这个函数中将父组件中的

    options

    和刚传入的子组件的

    options

    做合并并返回,然后赋值给父组件的

    options

    ,至此就完成了在父组件中给动态组件传值的过程;
  • 回到案例中动态创建的组件

    new Vue.extend(tab)

    得到实例

    Sub

    ,调用它并传入参数

    (new Vue.extend(tab))({propsData: {tabname: this.tabName}})

    ,最后调用

    $mount

    挂载组件,至此就完成了组件的动态创建和挂载;
  • 回顾一下

    Vue.extend

    其实就是Vue暴露了一个接口允许我们在一个父Vue实例下动态的去创建另外一个子Vue实例并挂载到父Vue上。

动态创建的组件怎么监听自定义事件

<div id="app" class="app">
    <div>XXX页面</div>
    <div id="tabBox" class="tabBox"></div>
    <div class="add" @click="add" ref="add">+</div>
  </div>

  <template id="tab">
    <div class="tab" @click="changeColor">{{tabname}}</div>
  </template>

  <script src="./lib/vue.js"></script>
  <script>
    const tab = {
      template: '#tab',
      props: ['tabname'],
      methods: {
        changeColor() {
          this.$emit('change-color')
        }
      }
    }

    const vm = new Vue({
      data: {
        tabName: '',
        base: '页签',
        num: 1
      },
      methods: {
        add() {
          this.tabName = this.base + this.num
          this.num++
          const tabCmp = new (Vue.extend(tab))({propsData: {tabname: this.tabName}}).$mount()
          // 直接这样监听自定义事件
          tabCmp.$on('change-color', () => {
            let refAdd = this.$refs.add
            refAdd.style.backgroundColor = 'red'
          })
          document.getElementById('tabBox').appendChild(tabCmp.$el)
        }
      }
    }).$mount('#app')

  </script>

  • 在之前案例的基础上给tab页签增加了一个点击事件

    changeColor



  • methods

    中添加函数并发射

    change-color

    事件;
  • 在动态创建组件的位置使用

    tabCmp.$on

    监听自定义事件;
  • 最后得到的效果是当用户点击

    +

    增加新的tab页签,用户点击页签,添加页签按钮背景变为红色。

封装动态创建组件的函数

function Create(components, propsData, parentNode) {
  this.cmp = null
  this.components = components
  this.propsData = propsData
  this._init()
  this._insert(parentNode)
}

Create.prototype._init = function() {
  this.cmp = new (Vue.extend(this.components))({propsData: this.propsData}).$mount()
}

Create.prototype._insert = function(parentNode) {
  parentNode.appendChild(this.cmp.$el)
}

Create.prototype.on = function(eventName, callback) {
  this.cmp.$on(eventName, callback)
}

使用封装的方法动态创建组件

const parent = document.getElementById('tabBox')
let create = new Create(tab, {tabname: this.tabName}, parent)
create.on('change-color', () => {
  let refAdd = this.$refs.add
  refAdd.style.backgroundColor = 'red'
})