愛總結(jié)胜蛉,愛搬磚,愛生活
引言
最近在板磚途中遇到一個(gè)需要?jiǎng)討B(tài)創(chuàng)建Vue組件并插入到頁面上的需求沮明,之前沒有遇到過這類需求辕坝,經(jīng)過一番百度搞定這個(gè)問題,在這里寫下總結(jié)荐健,作為回顧酱畅。
心得
動(dòng)態(tài)創(chuàng)建組件依靠Vue.extend
案例引入
現(xiàn)在有下面這樣一個(gè)需求需要用vue實(shí)現(xiàn),圖來...
<img src="https://note.youdao.com/yws/api/personal/file/C82414E778A24C87ADD9357392BF8025?method=download&shareKey=cb28fb3e8429d696a0c91fb3f6cb83cf" alt="案例引入">
案例分析
案例中用戶通過點(diǎn)擊新增按鈕江场,新加下一個(gè)頁簽纺酸,要實(shí)現(xiàn)這個(gè)需求只能通過動(dòng)態(tài)的創(chuàng)建組件,然后添加到頁面中扛稽,下面來實(shí)現(xiàn)這個(gè)需求(頁簽內(nèi)容部分就不做了吁峻,重點(diǎn)放在動(dòng)態(tài)創(chuàng)建頁簽按鈕)
案例實(shí)現(xiàn)
<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 >案例代碼</a>
代碼分析
- 創(chuàng)建一個(gè)vue頁面,頁面內(nèi)包含3個(gè)
div
在张,title
是頁面的標(biāo)題用含,tabBox
用來放所有的頁簽,add
監(jiān)聽用戶的點(diǎn)擊添加頁簽帮匾; - 局部注冊(cè)一個(gè)組件
tab
啄骇,傳入了template
和props
兩項(xiàng)屬性; - 在父組件的
methods
中添加add事件處理函數(shù)瘟斜,重點(diǎn)就在這個(gè)事件處理函數(shù)中缸夹,首先生成tabName
痪寻,然后利用Vue.extend
一番操作得到組件tabCmp
,最后將創(chuàng)建的組件tabCmp
插入到tabBox
這個(gè)元素中虽惭; - 當(dāng)目前為止橡类,當(dāng)用戶每次點(diǎn)擊
add
按鈕,便會(huì)在tabBox
中插入一個(gè)tab
組件,頁面上也會(huì)做更新芽唇; - 在這一連串的操作中核心就是
Vue.extend
,下面具體認(rèn)識(shí)以下這個(gè)API顾画。
關(guān)于Vue.extend
先看Vue官網(wǎng)對(duì)于這個(gè)API的解釋:
使用基礎(chǔ)
Vue
構(gòu)造器,創(chuàng)建一個(gè)“子類”匆笤。參數(shù)是一個(gè)包含組件選項(xiàng)的對(duì)象研侣。
data
選項(xiàng)是特例,需要注意 - 在Vue.extend()
中它必須是函數(shù)
- 通俗的講
Vue.extend
就是一個(gè)構(gòu)造函數(shù)炮捧,它可以生成一個(gè)Vue實(shí)例(子類)庶诡,可以像使用Vue構(gòu)造函數(shù)一樣使用他。特別注意的是他里面的data一定要是函數(shù)咆课,這一點(diǎn)很好理解隔離作用域末誓; - 將前面關(guān)鍵代碼拆分一下理解,如下:
const tabCmp = new Vue.extend({
template: "#tab",
props: ['tabname']
}).$mount()
- 這樣看就他跟
Vue
構(gòu)造函數(shù)沒區(qū)別,$mount()
負(fù)責(zé)掛載书蚪,不是這里的重點(diǎn)基显,不贅述; - 但這還不夠善炫,這還不能完成上面的動(dòng)態(tài)創(chuàng)建組件撩幽,
props
的值怎么傳到tabCmp
組件中,這里可以看一下Vue源碼中關(guān)于Vue. extend
的實(shí)現(xiàn)
Vue.extend的源碼實(shí)現(xiàn)
下面代碼片段是我從源碼中摘取出來的
// 取自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()
得到一個(gè)Sub
, 調(diào)用Sub
會(huì)執(zhí)行的_init
方法,_init
方法接受一個(gè)options對(duì)象箩艺,在這里只傳入propsData
就相當(dāng)于在標(biāo)簽中綁定了一個(gè)props值窜醉,繼續(xù)往下代碼會(huì)執(zhí)行mergeOptions()
,在這個(gè)函數(shù)中將父組件中的options
和剛傳入的子組件的options
做合并并返回,然后賦值給父組件的options
艺谆,至此就完成了在父組件中給動(dòng)態(tài)組件傳值的過程榨惰; - 回到案例中動(dòng)態(tài)創(chuàng)建的組件
new Vue.extend(tab)
得到實(shí)例Sub
,調(diào)用它并傳入?yún)?shù)(new Vue.extend(tab))({propsData: {tabname: this.tabName}})
静汤,最后調(diào)用$mount
掛載組件琅催,至此就完成了組件的動(dòng)態(tài)創(chuàng)建和掛載; - 回顧一下
Vue.extend
其實(shí)就是Vue暴露了一個(gè)接口允許我們?cè)谝粋€(gè)父Vue實(shí)例下動(dòng)態(tài)的去創(chuàng)建另外一個(gè)子Vue實(shí)例并掛載到父Vue上虫给。
動(dòng)態(tài)創(chuàng)建的組件怎么監(jiān)聽自定義事件
<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()
// 直接這樣監(jiān)聽自定義事件
tabCmp.$on('change-color', () => {
let refAdd = this.$refs.add
refAdd.style.backgroundColor = 'red'
})
document.getElementById('tabBox').appendChild(tabCmp.$el)
}
}
}).$mount('#app')
</script>
<a >案例代碼</a>
- 在之前案例的基礎(chǔ)上給tab頁簽增加了一個(gè)點(diǎn)擊事件
changeColor
藤抡; - 在
methods
中添加函數(shù)并發(fā)射change-color
事件; - 在動(dòng)態(tài)創(chuàng)建組件的位置使用
tabCmp.$on
監(jiān)聽自定義事件抹估; - 最后得到的效果是當(dāng)用戶點(diǎn)擊
+
增加新的tab頁簽缠黍,用戶點(diǎn)擊頁簽,添加頁簽按鈕背景變?yōu)榧t色药蜻。
封裝動(dòng)態(tài)創(chuàng)建組件的函數(shù)
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)
}
使用封裝的方法動(dòng)態(tài)創(chuàng)建組件
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 >案例代碼</a>
<a >create.js</a>
愛總結(jié)瓷式,愛搬磚替饿,愛生活