這個(gè)實(shí)現(xiàn)過程呢捺典,會(huì)涉及render序六、Vue.extend午衰、Vue插件等知識
分析需求
彈窗的特點(diǎn):
- 位置不相對某個(gè)元素,而是相對于整個(gè)視窗介劫,通常掛載于body,也就是要在vue的根實(shí)例app之外的案淋。這樣座韵,不會(huì)影響別的內(nèi)容的布局,也方便我們調(diào)整彈窗的位置
- 通過js創(chuàng)建的踢京,不需要在任何組件中聲明誉碴,即開即用型
大概是要這樣的效果:
// 可定制彈窗的標(biāo)題,內(nèi)容瓣距,以及顯示幾秒黔帕,提示的類型,這個(gè)有點(diǎn)類似element的toast
this.$notice({
title: "自定義彈窗標(biāo)題",
content: "彈窗內(nèi)容"蹈丸,
duration: 1000,
type: "success" // 類似element組件成黄,可選success, warning, error
})
實(shí)現(xiàn)過程
1. 先寫要實(shí)現(xiàn)的組件Notice組件
<!-- Notice.vue -->
<template>
<div ref="notice" class="notice-wrap">
<h1 class="title">{{ title }}</h1>
<p class="content">
<span class="iconfont" :class="iconType"></span>
<span class="text">{{ content }}</span>
</p>
</div>
</template>
<script>
export default {
name: "notice",
components: {},
props: {
// 標(biāo)題
title: {
type: String,
default: "提示",
},
// 提示內(nèi)容
content: {
type: String,
default: "內(nèi)容",
},
// 幾秒后,關(guān)閉彈窗逻杖,默認(rèn)1s
duration: {
type: Number,
default: 1000,
},
// 要顯示的圖標(biāo)的類型:success, warning ,error
type: {
type:String,
default: 'success'
}
},
data() {
return {
isShow: false,
}
},
computed: {
// 這里的圖標(biāo)奋岁,我是用iconfont來實(shí)現(xiàn),所以要先去官網(wǎng)把這個(gè)圖下下來: https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.d9df05512&cid=22664
// iconfont這里我就不詳細(xì)介紹了荸百,就是個(gè)圖標(biāo)闻伶,你不要這個(gè),直接注釋掉也不會(huì)影響你了解過程
iconType() {
if(this.type === 'success') {
return 'icon-success-filling'
}else if(this.type === 'warning'){
return 'icon-prompt-filling'
}else {
return 'icon-delete-filling'
}
}
},
methods: {}
};
</script>
<style scoped>
.notice-wrap {
/* height: 200px; */
width: 400px;
position: absolute;
top: 10%;
left: 30%;
border: 1px solid rgba(58,58,58, 0.2);
border-radius: 4px;
box-shadow: 10px 10px 5px #888888;
padding: 16px;
}
.title {
margin: 0;
font-size: 20px;
}
.content {
font-size: 14px;
}
.icon-success-filling{
color: green;
}
.icon-prompt-filling {
color: orange;
}
.icon-delete-filling {
color: red;
}
.text {
display: inline-block;
margin-left: 8px;
}
</style>
2. 將組件實(shí)例化够话,并轉(zhuǎn)化成真實(shí)dom
組件的實(shí)例化
一說到實(shí)例化蓝翰,學(xué)過面向?qū)ο蟮耐瑢W(xué)第一反應(yīng)就是構(gòu)造函數(shù)了。所以我們要先創(chuàng)建一個(gè)組件的構(gòu)造函數(shù)女嘲,將我們的Notice.vue組件(配置對象)轉(zhuǎn)化成一個(gè)虛擬節(jié)點(diǎn)畜份。然后再將虛擬節(jié)點(diǎn)轉(zhuǎn)化成真實(shí)dom,然后掛載到頁面上欣尼。
構(gòu)造函數(shù)的創(chuàng)建一般會(huì)有兩種方法:
使用render渲染函數(shù)
使用render渲染函數(shù)爆雹,在我們可能還不知道怎么操作時(shí),看看main.js文件中是怎么做的:
new Vue({
render: h => h(App),
}).$mount('#app')
其實(shí)它做這幾件事:
- render函數(shù)中媒至,h其實(shí)是createElement的意思顶别,因其頻繁使用,且在源碼中被命名為h, 固我們也就都叫h拒啰。h的作用是將xxx.vue組件轉(zhuǎn)化成了一個(gè)虛擬節(jié)點(diǎn)(VNode)驯绎。
- $mount的作用,是將VNode轉(zhuǎn)化為真實(shí)Dom谋旦,并掛載在指定的真實(shí)節(jié)點(diǎn)中剩失,這里屈尼,也就是掛載到App.vue組件中的id為app的div上,相當(dāng)于:
js document.getElememntById("app").appendChild(this.$el)
- 如果$mount的函數(shù)不寫任何參數(shù)(注意不能直接寫body,官方說了不允許K┕隆)脾歧,那么它依然會(huì)將VNode轉(zhuǎn)化為真實(shí)Dom,但是不進(jìn)行掛載追加演熟。生成的dom呢鞭执,我們可以在它的實(shí)例對象$el獲取
// notice/index.js
import Vue from 'vue'
import Notice from './Notice.vue'
function create(props) {
// 類似main.js中的用法
const vm = new Vue({
// props是傳給Notice組件中的props屬性
render: h => h(Notice, {props})
})
// 將vm轉(zhuǎn)化成真實(shí)dom
vm.$mount()
// 將真實(shí)dom,掛載到body上
document.body.appendChild(vm.$el)
}
export default create;
使用Vue.extends
學(xué)習(xí)東西呢芒粹,老樣子兄纺,官網(wǎng)先走一波,官網(wǎng)傳送門: https://cn.vuejs.org/v2/api/index.html#Vue-extend
這里化漆,白話解釋一下估脆,Vue.extend()其實(shí)就是用來創(chuàng)建組件的構(gòu)造函數(shù)的,然后使用這個(gè)構(gòu)造函數(shù)創(chuàng)建出Vue的虛擬節(jié)點(diǎn)Vnode
// notice/index.js
import Vue from 'vue'
import Notice from './Notice.vue'
function create(props) {
// 使用Vue.extend創(chuàng)建構(gòu)造函數(shù)座云,MyComponent是自定義的vue組件(MyComponent.vue)
const NoticeConstrutor = Vue.extend(Notice)
// 構(gòu)造函數(shù)的參數(shù)疙赠,propsData相當(dāng)于我們組件MyComponent.vue里需要的props,這里為了和vue文件中的props沖突朦拖,所以官方取了個(gè)別名
// 然后實(shí)例化后圃阳,會(huì)生成一個(gè)vue組件對應(yīng)的虛擬節(jié)點(diǎn)
const notice = new NoticeConstrutor({propsData:props})
// 有了實(shí)例后,最后和render一樣贞谓,使用$mount進(jìn)行掛載
notice.$mount()
// 將真實(shí)dom限佩,掛載到body上葵诈,注意裸弦,這里的實(shí)例變成notice
document.body.appendChild(notice.$el)
}
export default create;
到了這一步,我們就可以實(shí)現(xiàn)一個(gè)彈窗的功能了作喘,新建一個(gè)組件測試:
<!-- notice/test.vue -->
<script>
import create from '@/notice/index.js'
export default {
name: "Test",
mounted() {
create({
title: "測試彈窗",
content: "測試彈窗內(nèi)容",
duration: 2000,
type: "error",
});
},
}
</script>
3. 銷毀操作
我們彈窗設(shè)計(jì)理疙,肯定某個(gè)動(dòng)作后會(huì)觸發(fā),比如提交表單之類的泞坦,那么這個(gè)動(dòng)作肯定也會(huì)不只一次觸發(fā)窖贤,如果觸發(fā)多次后,就會(huì)多次調(diào)用create方法后贰锁,如果不做銷毀操作赃梧,就會(huì)一直往body上追加彈窗節(jié)點(diǎn),這不是我們想看到的豌熄,所以做一下收尾工作了:
- 將彈窗的dom從body上銷毀
- 將彈窗的實(shí)例對象銷毀授嘀,釋放內(nèi)存
import Vue from 'vue'
import Notice from './Notice.vue'
function create(props) {
// 2. 使用Vue.extend的方法創(chuàng)建
const NoticeCons = Vue.extend(Notice)
const notice = new NoticeCons({propsData: props})
notice.$mount()
document.body.appendChild(notice.$el)
// 添加銷毀操作
function remove() {
// 將真實(shí)dom節(jié)點(diǎn)干掉
document.body.removeChild(vm.$el)
// 將虛擬節(jié)點(diǎn)占的內(nèi)存也釋放掉
vm.$destroy()
}
// 在幾秒后,進(jìn)行銷毀操作
if(props.duration) {
setTimeout(() => {
remove()
}, props.duration)
}
}
export default create;
這時(shí)候锣险,再進(jìn)行測試蹄皱,就會(huì)發(fā)現(xiàn)彈窗2s后自動(dòng)消失了
4. 使用插件的方式览闰,將彈窗注入Vue原型上
彈窗肯定是不只一個(gè)地方會(huì)用到的,想想巷折,如果我們在多個(gè)文件里要用到彈窗压鉴,是不是每次都得:
import create from '@/notice/index.js'
create({ //...
});
- 很麻煩,我們就想像element的彈窗一樣锻拘,只使用this.$message就可以創(chuàng)建調(diào)用油吭,這時(shí)候插件就派上用場了:
// notice/index
import Vue from 'vue'
import Notice from './Notice.vue'
function create(props) {
// 1. 使用render
// 類似main.js中的用法
// const vm = new Vue({
// render: h => h(Notice, {props})
// })
// // 將vm轉(zhuǎn)化成真實(shí)dom
// vm.$mount()
// // 將真實(shí)dom,掛載到body上
// document.body.appendChild(vm.$el)
// console.log('vm.$el:', vm.$el);
// function remove() {
// // 將真實(shí)dom節(jié)點(diǎn)干掉
// document.body.removeChild(vm.$el)
// // 將虛擬節(jié)點(diǎn)占的內(nèi)存也釋放掉
// vm.$destroy()
// }
// 2. 使用Vue.extend
const NoticeCons = Vue.extend(Notice)
const notice = new NoticeCons({propsData: props})
notice.$mount()
document.body.appendChild(notice.$el)
// 將remove掛載到實(shí)例上署拟,這樣組件里上鞠,以后可以調(diào)用this.remove()來執(zhí)行這個(gè)方法
notice.remove = function() {
// 將真實(shí)dom節(jié)點(diǎn)干掉
document.body.removeChild(notice.$el)
// 將虛擬節(jié)點(diǎn)占的內(nèi)存也釋放掉
notice.$destroy()
}
if(props.duration) {
setTimeout(() => {
notice.remove()
}, props.duration)
}
return notice;
}
// 插件走一波
const noticePlugin = {
install: function(Vue, options) {
// 將這個(gè)方法掛載到Vue.prototype.$notice上,就可以使用this.$notice來調(diào)用了
Vue.prototype.$notice = create
}
}
export default noticePlugin;
- 在入口文件main.js中調(diào)用插件:
// main.js
import Vue from 'vue'
// 導(dǎo)入插件
import noticePlugin from '@/notice/index'
// 使用插件
Vue.use(noticePlugin)
new Vue({
router, // 注意key是小寫
store,
render: h => h(App),
}).$mount('#app')
- 這樣芯丧,調(diào)用時(shí)就可以省了導(dǎo)入操作芍阎,直接使用this.$notice來調(diào)用
<!-- notice/test.vue -->
<script>
export default {
name: "Test",
mounted() {
this.$notice({
title: "測試彈窗",
content: "測試彈窗內(nèi)容",
duration: 2000,
type: "error",
});
},
methods: {},
};
</script>