我們?cè)趘ue項(xiàng)目開(kāi)發(fā)過(guò)程中總是遇到各種個(gè)樣的全局彈窗(權(quán)限提醒磅叛,alert提示蔗衡,繳費(fèi)提醒),這些全局彈窗全站很多地方在調(diào)用嘀韧,調(diào)用調(diào)用很蠻煩江解,將這些組件部署到全局位置,又無(wú)法做到按需加載琅拌,而且部署到全局的組件數(shù)據(jù)回傳(比如檢測(cè)繳費(fèi)是否完成)又是一個(gè)問(wèn)題,我們又得借助vuex或者event來(lái)傳遞,又回造成vuex的濫用掂林。
我們舉一個(gè)簡(jiǎn)單的場(chǎng)景:
如果我們要在vue里實(shí)現(xiàn)一個(gè)類似原生alert的彈出框,大概長(zhǎng)這個(gè)樣子坝橡。
<template>
<div class="popup_background">
<div>
<div v-html="title"></div>
<button @click="ok">確定</button>
<button @click="cancel">否定</button>
</div>
</div>
</template>
<script>
export default {
props: ['text'],
data() {
return {};
},
methods: {
ok() {
this.$emit('ok');
},
cancel() {
this.$emit('cancel');
}
}
}
</script>
如果有一個(gè)頁(yè)面泻帮,需要使用這個(gè)組件,即使是空頁(yè)面计寇,也要做下面代碼里描述的所有事情
<template>
<div>
<button @click="showAlert">打開(kāi)alert</button>
<ui-alert v-if="isShow" title="這是標(biāo)題" @ok="fine" @cancel="cancel"></ui-alert>
</div>
</template>
<script>
import uiAlert from '******.vue'; // 引用組件
export default {
data() {
return {
isShow:false //控制彈窗是否顯示的開(kāi)關(guān)
};
},
methods: {
showAlert() { //控制彈窗顯示
this.isShow = true;
},
fine() { //彈窗打開(kāi)后點(diǎn)擊ok的事件綁定
this.isShow = false;
// 點(diǎn)擊ok觸發(fā)的事情
},
cancel() { //彈窗打開(kāi)后點(diǎn)擊cancel的事件綁定
this.isShow = false;
// 點(diǎn)擊cancel觸發(fā)的事情
}
},
components:{uiAlert}
}
</script>
匯總一下锣杂,做的事情有
1脂倦、引用組件
2、設(shè)置控制顯示的變量開(kāi)關(guān)
3元莫、設(shè)置彈出組件的函數(shù)
4赖阻、設(shè)置多個(gè)回調(diào)事件,監(jiān)聽(tīng)返回值
但是我們想一下踱蠢,假如我們可以像調(diào)用原生組件類似的形式火欧,一個(gè)函數(shù)搞定,會(huì)更方便茎截。獲取返回值的方式比如promise苇侵、回調(diào)。比如我們封裝成返回promise的一個(gè)函數(shù)企锌,代碼可能像下面這樣榆浓。
<template>
<div>
<button @click="showAlert">打開(kāi)alert</button>
</div>
</template>
<script>
import popAlert from '*******';
export default {
methods: {
showAlert() {
popAlert('這是標(biāo)題').then(res=>{
// res 就是true或者false,代表點(diǎn)擊了ok還是cancel
})
}
}
}
</script>
這樣調(diào)用起來(lái)很方便霎俩,我們可以看看類似alert這樣的彈出框哀军,有一些特點(diǎn),就是過(guò)程化打却,它從被創(chuàng)建到使用杉适,再到最后銷毀,這個(gè)過(guò)程相對(duì)調(diào)用的頁(yè)面是獨(dú)立的柳击,交互過(guò)程是高度過(guò)程化的猿推。不像其他組件和頁(yè)面直接可能多次交互。簡(jiǎn)單來(lái)說(shuō)捌肴,就是“用完即刪”蹬叭。再有就是布局獨(dú)立,布局層面跟調(diào)用者不存在占位歸屬状知,更像是全局的組件秽五。
只要符合這種特點(diǎn)的組件,封裝成函數(shù)饥悴,是更方便的策略坦喘。
那么如何封裝出一個(gè)類似剛才例子中popAlert這樣的函數(shù)呢?我們要保留組件要使用vue的開(kāi)發(fā)方式西设,也就是說(shuō)瓣铣,我們要把已經(jīng)存在的vue組件不進(jìn)行任何改動(dòng),在組件外對(duì)它通過(guò)包裝的形式形成一個(gè)函數(shù)贷揽。這樣vue組件可以向前兼容棠笑,并且組件繼續(xù)使用vue的開(kāi)發(fā)方式。
全局按需動(dòng)態(tài)加載
我想到的實(shí)現(xiàn)方式禽绪,就是利用render函數(shù)蓖救。我們?cè)谝粋€(gè)“全局”的地方洪规,所有頁(yè)面都會(huì)加載的最外層的模板,比如跟路由配置的那個(gè)App.vue循捺。在這里添加我們要開(kāi)發(fā)的淹冰,可以動(dòng)態(tài)改變狀態(tài)的組件。
<template>
<div id="app">
<router-view/>
<!--全局的地方添加一個(gè)組件-->
<dynamic-vue></dynamic-vue>
</div>
</template>
.........
我們看一下dynamic-vue的實(shí)現(xiàn)
<!--一個(gè)用于創(chuàng)建臨時(shí)vue對(duì)象的容器巨柒,例如選擇彈窗樱拴,提示窗,或其他場(chǎng)景下使用的"用完即刪"型需求-->
<script>
export default {
data() {
return {
globleVue: null // 臨時(shí)動(dòng)態(tài)添加的窗體洋满,例如彈窗等
/*
{
vueObj: 'vue組件',
attr:{ //初始化組件的props
},
on:{ //組件的emit回調(diào)綁定
}
}
*/
}
},
render(createElement) {
if(this.globleVue){
return createElement(this.globleVue.vueObj, {
props: this.globleVue.attr,
on: this.globleVue.event
});
}
else {
return createElement('div');
}
}
}
</script>
接下來(lái)我們只要能夠通過(guò)某些方法把globleVue賦值成包含vue組件的對(duì)象晶乔,初始化的參數(shù),回調(diào)方法的對(duì)象牺勾,全局就會(huì)彈出這個(gè)組件正罢。我的實(shí)現(xiàn)方式是使用自帶的events對(duì)globleVue進(jìn)行修改。我們?cè)赾reated里面增加event的監(jiān)聽(tīng)驻民。
<script>
import { dynamicVueEvent } from 'dynamicVueObject.js';
export default {
created() {
// 支持全局調(diào)用臨時(shí)添加翻具,用完即刪型vue組件
dynamicVueEvent.on('addVueObj', (vueObj, attr, event) => {
this.globleVue = {
vueObj,
attr,
event
};
});
dynamicVueEvent.on('deleteVueObj', () => {
this.globleVue = null;
});
},
data() {
return {
globleVue: null
}
},
render(createElement) {
if(this.globleVue){
return createElement(this.globleVue.vueObj, {
props: this.globleVue.attr,
on: this.globleVue.event
});
}
else {
return createElement('div');
}
}
}
</script>
觸發(fā) dynamicVueEvent 事件的代碼。
// dynamicVueObject.js
import Vue from 'vue';
var events = require('events');
var eventEmitter = new events.EventEmitter();
export function createVueObj(vueClass, attr, event) {
let vueObj = Vue.extend(vueClass);
dynamicVueEvent.emit('addVueObj', vueObj, attr, event);
}
export function deleteVueObj() {
dynamicVueEvent.emit('deleteVueObj');
}
dynamicVueObject.js暴露了兩個(gè)函數(shù)回还,一個(gè)動(dòng)態(tài)創(chuàng)建組件createVueObj裆泳,一個(gè)清空組件deleteVueObj。我們就有了在全局動(dòng)態(tài)控制彈出組件的這個(gè)功能柠硕。我們可以在任何地方通過(guò)這兩個(gè)函數(shù)控制全局彈窗了工禾。
接下來(lái)我們就可以對(duì)已經(jīng)存在的alert.vue組件,不進(jìn)行修改的基礎(chǔ)上蝗柔,單獨(dú)封裝一個(gè)獨(dú)立的函數(shù)闻葵。
我們?cè)谠嫉腶lert.vue旁邊創(chuàng)建一個(gè)alert.js,這樣調(diào)用者很容易發(fā)現(xiàn)和調(diào)用癣丧。
/*
alert.js
alert.vue組件函數(shù)化
*/
// 引入全局創(chuàng)建組件槽畔,和全局刪除組件兩個(gè)方法
import { createVueObj, deleteVueObj } from '../dynamicVueObject/dynamicVueObject';
import alertVue from './alert.vue'; // 引入要使用的組件
export function alert(text) {
return new Promise((resolve, reject) => {
let dyVueObj = createVueObj(alertVue, {
text
}, {
ok() {
resolve(true);
deleteVueObj(dyVueObj); // 刪除掉彈出的組件
},
cancel() {
resolve(false);
deleteVueObj(dyVueObj); // 刪除掉彈出的組件
}
});
});
}
這樣alert函數(shù)封裝完畢了。不到20行就可以針對(duì)alert.vue封裝一個(gè)promise方式的alert胁编,很方便厢钧,接下來(lái)我們就可以調(diào)用一下這個(gè)alert函數(shù)
<template>
<div>
<button @click="showAlert">打開(kāi)alert</button>
</div>
</template>
<script>
import popAlert from '****/alert.js';
export default {
methods: {
showAlert() {
popAlert('這是標(biāo)題').then(res=>{
// res 就是true或者false,代表點(diǎn)擊了ok還是cancel
})
}
}
}
</script>
這樣調(diào)用就方便多了L秃簟;悼臁铅檩!
大概實(shí)現(xiàn)邏輯就是如此了憎夷。
后續(xù)做的優(yōu)化
1、不再使用手動(dòng)部署dynamicVueObject.vue到一個(gè)全局的地方昧旨。
不再使用手動(dòng)部署dynamicVueObject.vue到一個(gè)全局的地方拾给,而是在dynamicVueObject.js第一次執(zhí)行的時(shí)候祥得,在body上添加一個(gè)動(dòng)態(tài)創(chuàng)建的新div上。
let dyVueObj = Vue.extend({
/*****dynamicVueObject.vue******/
});
// 添加到一個(gè)動(dòng)態(tài)的div上
let instance = new dyVueObj({
el: document.createElement('div'),
});
// 添加到body上
document.body.appendChild(instance.$el);
export function createVueObj(vueClass, attr, event) {
let vueObj = Vue.extend(vueClass);
dynamicVueEvent.emit('addVueObj', vueObj, attr, event);
return vueObj;
}
export function deleteVueObj(obj) {
dynamicVueEvent.emit('deleteVueObj', obj);
}
2蒋得、支持同時(shí)彈出多個(gè)組件
allGlobleVue改為了一個(gè)數(shù)組级及,支持添加多個(gè)彈出組件createVueObj方法的返回值,作為deleteVueObj刪除方法的參數(shù)额衙。
let dyVueObj = Vue.extend({
created() {
// 支持全局調(diào)用臨時(shí)添加饮焦,用完即刪型vue組件
dynamicVueEvent.on('addVueObj', (vueObj, attr, event) => {
this.allGlobleVue.push({
vueObj, attr, event
});
});
dynamicVueEvent.on('deleteVueObj', (vueObj) => {
this.allGlobleVue.splice(this.allGlobleVue.findIndex(item => {
return item.vueObj === vueObj
}), 1);
});
},
data() {
return {
allGlobleVue: [] // 臨時(shí)動(dòng)態(tài)添加的窗體,例如彈窗等
}
},
render(createElement) {
return createElement('div', {}, this.allGlobleVue.map(item => {
return createElement(item.vueObj, {
props: item.attr,
on: item.event
})
}));
}
});
完整代碼的git倉(cāng)庫(kù)是
https://github.com/13601313270/dynamicVueObject
完整的功能窍侧,支持同時(shí)彈出多個(gè)組件县踢,git倉(cāng)庫(kù)里包含一個(gè)可以跑起來(lái)的demo。