GIF.gif
前言
最近在做移動(dòng)端的項(xiàng)目镰吵,需要制作移動(dòng)端的alert彈框和message-box提示信息;之前使用Vue框架的element-ui時(shí)壶硅,就記得element-ui的彈框梨树,今天深入的研究了一下源碼,然后簡(jiǎn)單制作了一點(diǎn)小demo
用到的知識(shí)點(diǎn)
Vue組件的定義涨共,Vue的extend
Vue.extend(options)
options參數(shù)是一個(gè)對(duì)象纽帖,一個(gè)Vue組件配置項(xiàng)的對(duì)象,例如
// 創(chuàng)建一個(gè)構(gòu)造器举反,
const Profile = Vue.extend({
// 模板使用參數(shù)內(nèi)的template模板
template: `<div>{{message}}</div>`,
// data內(nèi)的為默認(rèn)數(shù)據(jù)
data() {
return {
message: ''
}
}
})
// 創(chuàng)建一個(gè)基于構(gòu)造器的Vue實(shí)例抛计,并且給data重新賦值
const Message = new Profile({
data: {
message: 'message'
}
})
// 將這個(gè)實(shí)例掛載到 id 為 app 的DOM節(jié)點(diǎn)上
Message.$mount('#app');
正文
看完了基本原理,大部分同學(xué)應(yīng)該就已經(jīng)可以明白這個(gè)東西是怎么實(shí)現(xiàn)的了吧照筑!吹截,接著我?guī)е蠹襾?lái)看看具體的實(shí)現(xiàn)步驟
首先需要先定義一個(gè)Vue的組件瘦陈,這個(gè)組件不直接掛載到任何DOM節(jié)點(diǎn)中,而是作為一個(gè)模板波俄,一個(gè)構(gòu)造器來(lái)使用
<template>
<transition name="fade">
<!-- 使用fade淡入淡出動(dòng)畫晨逝,使用type變量來(lái)控制class類名,達(dá)到更改type值就可以修改樣式的效果 -->
<div :class="['plugins-message-box',type]" v-show="visible">
<!-- 使用iconClass來(lái)控制icon的類名懦铺,我使用的是阿里的字體圖標(biāo)庫(kù)iconfont捉貌,可以根據(jù)個(gè)人愛(ài)好來(lái)更換 -->
<div :class="['message-icon','iconfont',iconClass]"></div>
<!-- 輸出消息 -->
<div class="message-container">{{message}}</div>
</div>
</transition>
</template>
<script>
// 定義每一個(gè)type對(duì)應(yīng)的class類名
const typeClass = {
success: 'icon-success',
error: 'icon-error',
default: 'icon-success'
};
export default {
name: "messageMain",
// 定義的是默認(rèn)數(shù)據(jù),默認(rèn)值
data() {
return {
visible: false, // 控制DOM顯示隱藏
type: 'default', // 默認(rèn)type值為default
icon: '', // 默認(rèn)使用icon為空冬念,則使用type值對(duì)應(yīng)的icon
message: '', // 默認(rèn)的message為空趁窃,由外部傳入
duration: 2000 // 默認(rèn)顯示時(shí)間為2000ms
}
},
computed: {
// 如果外部傳入icon則使用外部的icon,如果沒(méi)有急前。則使用type值對(duì)應(yīng)的icon
iconClass() {
if(this.icon) {
return this.icon;
}else {
return typeClass[this.type];
}
}
}
}
</script>
可以看到這是一個(gè)非常簡(jiǎn)單的Vue組件模板醒陆,使用的是vue-cli工具構(gòu)建的,最終它拋出了一個(gè)對(duì)象裆针,而我們接著就應(yīng)該來(lái)定義構(gòu)造器
import Vue from 'vue'; // 引入Vue
import MessageMain from './messageMain'; // 引入上邊定義好的message模板
const MessageBox = Vue.extend(MessageMain); // 使用Vue.extend來(lái)創(chuàng)建一個(gè)構(gòu)造器
let instance; // instance 變量用來(lái)保存實(shí)例
let timer = null; // timer 變量用來(lái)保存定時(shí)器
// 定義一個(gè)function刨摩,參數(shù)為options,默認(rèn)為一個(gè)對(duì)象
const Message = function(options = {}) {
// 如果當(dāng)前處在服務(wù)器端世吨,則直接返回
if(Vue.prototype.$isServer) return;
// 如果當(dāng)前定時(shí)器已開啟澡刹,說(shuō)明頁(yè)面上已經(jīng)有一個(gè)message-box了,則不能再繼續(xù)創(chuàng)建新的message-box
if(timer) return;
// 對(duì)options做處理耘婚,如果直接傳入string罢浇,則使其保存在options的message屬性上
if(typeof options === 'string') {
options = {
message: options
}
}
// 初始化實(shí)例,并將options作為新的data傳入沐祷,Vue會(huì)將options合并到原有的data上己莺,覆蓋原有的默認(rèn)值,但是戈轿,在options中沒(méi)有設(shè)置的是不會(huì)被改變的
instance = new MessageBox({
data: options
});
// 調(diào)用$mount方法凌受,將當(dāng)前實(shí)例渲染為真實(shí)DOM,生成$el思杯,胜蛉,如果不執(zhí)行這一步,將拿不到 $el 的值色乾,但是不指定DOM節(jié)點(diǎn)接管當(dāng)前實(shí)例
instance.vm = instance.$mount();
// 使用原生js的API將當(dāng)前實(shí)例的真實(shí)DOM追加到body中
document.body.appendChild(instance.vm.$el);
// 實(shí)例上的vm就是我們的Vue組件誊册,所以我們可以通過(guò)vm訪問(wèn)到當(dāng)前實(shí)例中的所有屬性
// 將visible設(shè)置為true,即顯示當(dāng)前message-box
instance.vm.visible = true;
// 開啟定時(shí)器
timer = setTimeout(() => {
// 在時(shí)間結(jié)束后將當(dāng)前實(shí)例手動(dòng)卸載
instance.vm.$destroy();
// 使用原生API將當(dāng)前實(shí)例生成的DOM節(jié)點(diǎn)在真實(shí)的DOM樹中刪除
instance.vm.$el.parentNode.removeChild(instance.vm.$el);
// 清除定時(shí)器
timer = null;
}, instance.vm.duration);
// 定時(shí)器的時(shí)間使用vm中定義的時(shí)間
return instance.vm;
};
// 最終拋出一個(gè)對(duì)象暖璧,對(duì)象上我們可以使用 install 來(lái)擴(kuò)展Vue的插件
// 當(dāng)我們的對(duì)象上有install方法的時(shí)候案怯,它接收第一個(gè)參數(shù)為Vue,
// 我這里為了方便使用澎办,還在當(dāng)前拋出的對(duì)象上定義了一個(gè)message方法嘲碱,為了方便在axios的攔截器中使用金砍;
export default {
message: Message,
install(Vue) {
Vue.prototype.$message = Message;
Vue.message = Message;
}
};
接著來(lái)看一下使用方法,首先要在main.js中將這個(gè)插件安裝到Vue上
import Message from './plugins/Message';
Vue.use(Message); // 因?yàn)槲覀兊膶?duì)象上定義了install方法,所以可以直接調(diào)用Vue的use方法
安裝完成之后麦锯,我們就可以在任意組件內(nèi)使用了恕稠,這個(gè)是我們剛剛在文章開頭看到的頁(yè)面,現(xiàn)在只有調(diào)用message-box的方法扶欣;
我們直接使用this.$message就可以調(diào)用我們的message-box鹅巍,并且顯示都沒(méi)有任何問(wèn)題,當(dāng)然料祠,也可以通過(guò)傳入icon和duration來(lái)修改圖標(biāo)和時(shí)間
<template>
<div class="test">
<div class="test-message">
<button @click="messageClick('default')">
default
</button>
<button @click="messageClick('success')">
success
</button>
<button @click="messageClick('error')">
error
</button>
</div>
</div>
</template>
<script>
export default {
name: "test",
methods: {
messageClick(type) {
this.$message({
type,
message: type
})
}
}
}
</script>
GIF.gif
看完了message-box骆捧,我想你對(duì)alert的做法也有了一定的想法了吧!接著我們來(lái)看一下alert的做法
在alert中髓绽,我們不需要定義type敛苇,也不需要修改樣式,
對(duì)于alert來(lái)說(shuō)梧宫,我們需要有一個(gè)頭部,一個(gè)內(nèi)容區(qū)摆碉,還有一個(gè)確認(rèn)按鈕塘匣,
而我們的內(nèi)容區(qū),使用的是v-html來(lái)渲染內(nèi)容巷帝,便于我們可以使用原生的DOM標(biāo)簽來(lái)實(shí)現(xiàn)一些簡(jiǎn)單的樣式
而我們?cè)谶@個(gè)組件內(nèi)忌卤,當(dāng)點(diǎn)擊確定按鈕的時(shí)候,我們將卸載當(dāng)前組件楞泼,并將當(dāng)前組件從DOM結(jié)構(gòu)刪除
<template>
<transition name="fade">
<div class="plugins-alert-box" v-show="visible">
<div class="plugins-alert-container">
<div class="alert-box-title">
{{title}}
</div>
<div class="alert-box-message" v-html="message">
</div>
<div class="alert-box-btn">
<button @click="btnClick">確定</button>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
name: "alertMain",
data() {
return {
visible: false,
message: '',
title: '提示'
}
},
methods: {
// 點(diǎn)擊確定按鈕的時(shí)候驰徊,首先隱藏當(dāng)前元素,然后將當(dāng)前元素從DOM結(jié)構(gòu)中刪除堕阔,并手動(dòng)卸載當(dāng)前實(shí)例
btnClick() {
this.visible = false;
this.$el.parentNode.removeChild(this.$el);
this.$destroy();
}
}
}
</script>
接著我們?cè)賮?lái)看定義構(gòu)造器的代碼
import Vue from 'vue'; // 引入Vue
import alertMain from './alertMain'; // 引入定義好的alert的模板
const AlertBox = Vue.extend(alertMain); // 創(chuàng)建一個(gè)構(gòu)造器
let instance; // instance 變量用來(lái)保存實(shí)例
// 定義 Alert函數(shù)棍厂,接收options參數(shù),默認(rèn)為一個(gè)對(duì)象
const Alert = function(options = {}) {
if(Vue.prototype.$isServer) return; // 判斷當(dāng)前如果是在服務(wù)器端渲染的話超陆,直接返回
// 同樣的牺弹,我們還要處理options
if(typeof options === 'string') {
options = {
message: options
}
}
// 創(chuàng)建一個(gè)alert-box實(shí)例 ,將options作為新的data傳入
instance = new AlertBox({
data: options
});
// 調(diào)用$mount方法时呀,創(chuàng)建真實(shí)DOM张漂,獲取到vm
instance.vm = instance.$mount();
// 調(diào)用原生API將當(dāng)前實(shí)例的真實(shí)DOM追加到body中
document.body.appendChild(instance.vm.$el);
// 讓當(dāng)前DOM顯示
instance.vm.visible = true;
return instance.vm;
};
// 拋出對(duì)象,以及install方法谨娜,原理我們?cè)谏线呉呀?jīng)講過(guò)了
export default {
alert: Alert,
install(vue) {
vue.prototype.$alert = Alert;
}
}
接著就是alert-box的使用方法了航攒,同樣是需要先use一下
import Alert from './plugins/Alert';
Vue.use(Alert);
最后來(lái)使用一下這個(gè)alert-box
<template>
<div class="test">
<div class="test-message">
<button @click="alertClick">alert</button>
</div>
</div>
</template>
<script>
export default {
name: "test",
methods: {
alertClick() {
this.$alert({
message: `<b>加粗文字</b>`
})
}
}
}
</script>
GIF.gif