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

爱总结,爱搬砖,爱生活

引言

最近在板砖途中遇到一个需要动态创建Vue组件并插入到页面上的需求,之前没有遇到过这类需求,经过一番百度搞定这个问题,在这里写下总结,作为回顾。

心得

动态创建组件依靠Vue.extend

案例引入

现在有下面这样一个需求需要用vue实现,图来...
<img src="https://note.youdao.com/yws/api/personal/file/C82414E778A24C87ADD9357392BF8025?method=download&shareKey=cb28fb3e8429d696a0c91fb3f6cb83cf" alt="案例引入">

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

案例实现

<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>

<a href="https://github.com/kuanyan9527/blog/blob/main/Vue/demo/%E5%8A%A8%E6%80%81%E5%88%9B%E5%BB%BA%E7%BB%84%E4%BB%B6.html">案例代码</a>

代码分析

  1. 创建一个vue页面,页面内包含3个divtitle是页面的标题, tabBox用来放所有的页签,add监听用户的点击添加页签;
  2. 局部注册一个组件tab,传入了templateprops两项属性;
  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>

<a href="https://github.com/kuanyan9527/blog/blob/main/Vue/demo/%E5%8A%A8%E6%80%81%E5%88%9B%E5%BB%BA%E7%BB%84%E4%BB%B6.html">案例代码</a>

  • 在之前案例的基础上给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'
})

<a href="https://github.com/kuanyan9527/blog/blob/main/Vue/demo/%E5%8A%A8%E6%80%81%E5%88%9B%E5%BB%BA%E7%BB%84%E4%BB%B62.html">案例代码</a>
<a href="https://github.com/kuanyan9527/blog/blob/main/Vue/demo/create.js">create.js</a>
爱总结,爱搬砖,爱生活

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容