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

该博客详细介绍了如何在Vue中通过Vue.extend动态创建组件,并将其添加到页面中。通过案例分析,展示了如何监听并响应动态创建组件的自定义事件。最后,对Vue.extend的源码进行了简单解析,并封装了一个动态创建组件的函数。

动态创建组件依靠  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个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>



 

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

 



 

Vue动态创建挂载组件实例是常见的开发需求,尤其适用于需要按需加载、弹窗组件、全局提示等场景。Vue 提供了多种 API 来实现这一功能,具体方法如下。 ### ### 使用 `Vue.extend` 创建组件构造器(Vue 2.x) 在 Vue 2.x 中,可以通过 `Vue.extend` 方法将一个组件选项对象转换为组件构造器,随后使用该构造器动态创建组件实例。例如: ```javascript import MyComponent from './MyComponent.vue'; const MyComponentConstructor = Vue.extend(MyComponent); const componentInstance = new MyComponentConstructor(); componentInstance.$mount(); // 挂载到 DOM 元素 document.body.appendChild(componentInstance.$el); ``` 这种方式允许开发者以编程方式控制组件的生命周期和挂载位置 [^1]。 ### ### 使用 `createApp` 和 `$mount` 实现动态挂载(Vue 3) 在 Vue 3 中,推荐使用 `createApp` 创建应用实例,通过 `mount` 方法将组件挂载到指定的 DOM 节点上。例如: ```javascript import { createApp } from 'vue'; import MyComponent from './MyComponent.vue'; const app = createApp(MyComponent); const mountNode = document.createElement('div'); document.body.appendChild(mountNode); app.mount(mountNode); ``` 此方式适用于创建独立的组件实例将其插入页面中的任意位置,适合构建自定义弹窗、全局通知等组件 [^2]。 ### ### 动态创建多个组件实例 如果需要在同一页面动态创建挂载多个组件实例,可以在每次调用时生成新的组件构造器或应用实例,分别挂载到不同的 DOM 节点上。例如: ```javascript for (let i = 0; i < 5; i++) { const MyComponentConstructor = Vue.extend(MyComponent); const componentInstance = new MyComponentConstructor(); componentInstance.$mount(); document.getElementById(`container-${i}`).appendChild(componentInstance.$el); } ``` 该方式可以灵活控制每个组件的挂载位置和行为 [^1]。 ### ### 异步加载组件与按需渲染 为了优化性能,特别是对于大型项目或懒加载场景,可以结合异步组件技术动态加载组件。在 Vue 2 中可使用工厂函数返回组件,而在 Vue 3 中则可结合 `defineAsyncComponent` 实现: ```javascript // Vue 2.x const AsyncComponent = Vue.extend({ components: { MyComponent: () => import('./MyComponent.vue') } }); // Vue 3 import { defineAsyncComponent } from 'vue'; const AsyncComponent = defineAsyncComponent(() => import('./MyComponent.vue') ); ``` 通过这种方式,组件只会在需要时被加载和渲染,从而提升应用启动性能 [^1]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值