在平時的開發過程當中,咱們老是先寫好一個組件,而後在須要的頁面中用 import
引入便可,但若是是下面這種類型的組件呢👇 javascript
this.$toast({
duration: 3000,
content: '這是一條消息提示'
});
複製代碼
沒錯,就是這麼簡單的一句話就萬事大吉了(就是用 js 調用組件而已啦🧐)。那這種效果到底是怎麼實現的呢?今天就讓咱們來(手把手🤝 )一探究竟吧!css
不知道小夥伴們有沒有用過 Vue.extend()
這個東東,反正我是不多碰過,印象不深,因此這裏咱們先來短暫瞭解一下 Vue.extend()
主要是用來幹嗎的。先來個官方說明(很少的,堅持下): html
// 導入以往的普通組件
import Main from './main.vue';
// 用 Vue.extend 建立組件的模板(構造函數)
let mainConstructor = Vue.extend(Main);
// 實例化組件
let instance = new mainConstructor();
// 掛載到相應的元素上
instance.$mount('#app');
複製代碼
不知道你看懂沒有,上面的 Vue.extend(Main)
就是一個基於 main.vue 的組件模板(構造函數),instance
是實例化的組件,$mount()
是手動掛載的意思。其中 Vue.extend()
和 $mount()
就是咱們經過 js 調用、渲染並掛載組件的精髓所在,至關於早前的 createElement
和 appendChild
,有殊途同歸之效。這個點須要咱們好好熟悉一下,因此你能夠先停下來屢屢思路🤔。
補充一下🤐:$mount()
裏面若是沒有參數,說明組件只是渲染了但尚未掛載到頁面上,若是有正確的(元素)參數則直接掛載到元素下面。前端
js 調用歸調用,最原始的組件仍是要有的,只是咱們不經過 import
來引入到頁面中而已。ok,咱們就以最開始的那個 toast 圖片來簡單寫一下這個 vue 組件(message 和 alert 也是同樣的)。這裏就直接上代碼啦,畢竟它的結構簡單到爆了,也不是本章節的重點:vue
<!-- main.vue -->
<template>
<div class="toast">
<p>服務器錯誤,請稍後重試</p>
</div>
</template>
<script> export default { name: "Toast", mounted() { setTimeout(() => { // 3s 後經過父級移除子元素的方式來移除該組件實例和 DOM 節點 this.$destroy(true); this.$el.parentNode.removeChild(this.$el); }, 3000); } }; </script>
<style lang="scss" scoped> .toast { display: flex; align-items: center; justify-content: center; position: fixed; top: 0; bottom: 0; left: 0; right: 0; color: #fff; z-index: 9999; background: transparent; > p { padding: 12px 22px; font-size: 18px; border-radius: 4px; background: rgba(17, 17, 17, 0.7); } } </style>
複製代碼
上面的內容想必你們應該都能看懂,因此這裏就直接講下面的重點了。java
咱們在 main.vue 的同級目錄下新建一個 main.js 文件。咱們先瞟一眼文件內容(也很少,已是個最簡版了)👇:vue-cli
// main.js
import Vue from "vue"; // 引入 Vue 是由於要用到 Vue.extend() 這個方法
import Main from "./main.vue"; // 引入剛纔的 toast 組件
let ToastConstructor = Vue.extend(Main); // 這個在前面的前置知識內容裏面有講到
let instance;
const Toast = function() {
instance = new ToastConstructor().$mount(); // 渲染組件
document.body.appendChild(instance.$el); // 掛載到 body 下
};
export default Toast;
複製代碼
上面的代碼暴露了一個 Toast 函數。爲何要暴露一個函數呢?緣由很簡單:你想一想,咱們最終是否是要根據 this.$toast()
來調用一個組件,說白了,經過 js 調用,本質就是調用一個 函數。也就是說 this.$toast()
就是執行了上面代碼中導出的 export default Toast
,也就是執行了 Toast 函數(const Toast = function() {}
),因此當咱們調用 this.$toast()
的時候其實就是執行了 Toast()
函數。而 Toast()
函數只作了一件事情:就是經過手動掛載的方式把組件掛載到 body 下面。
補充一下🤐:通常來講咱們常見的是 $mount("#app")
,也就是把組件掛載到 #app
下面,<router-view />
也包含在 #app
中,可是咱們這種 toast 提示是放在 body
下面的,也就是說它不受 #app
和 <router-view />
的管控,因此當咱們切換頁面(路由)的時候,這個 toast 組件是不會跟着立馬消失的,這點要注意哦😯。
這裏順便給個組件的目錄結構,以下圖所示: 後端
調用方式很簡單,首先咱們在入口文件 main.js(和上面不是同一個😢) 里加上兩行代碼,這樣咱們就能在須要的地方直接用 js 調用它了,以下圖所示: bash
別急,咱們好像還漏了點什麼🤔。。。對了,如今還不支持傳參呢,直接調用 this.$toast()
就只能顯示————服務器錯誤,請稍後重試(這下全都是後端的鍋了😊)。但咱們但是個有追求的前端,不能侷限於此,因此如今讓咱們來嘗試增長下兩個可配置參數,這裏拿 duration
和 content
舉個栗子🌰。
首先咱們要修改 main.vue 組件裏面的內容(其實沒啥大變化),就像下面這樣:服務器
<!-- main.vue 可配置版 -->
<template>
<div class="toast">
<p>{{ content }}</p>
</div>
</template>
<script> // 主要就改了 data export default { name: "Toast", data() { return { content: "", duration: 3000 }; }, mounted() { setTimeout(() => { this.$destroy(true); this.$el.parentNode.removeChild(this.$el); }, this.duration); } }; </script>
複製代碼
上面的代碼應該算是淺顯易懂了,接下來咱們看下 main.js 裏面改了啥:
// main.js 可配置版
import Vue from "vue";
import Main from "./main.vue";
let ToastConstructor = Vue.extend(Main);
let instance;
const Toast = function(options = {}) { // 就改了這裏,加了個 options 參數
instance = new ToastConstructor({
data: options // 這裏的 data 會傳到 main.vue 組件中的 data 中,固然也能夠寫在 props 裏
});
document.body.appendChild(instance.$mount().$el);
};
export default Toast;
複製代碼
其實 main.js 也沒多大變化,就是在函數裏面加了個參數。要注意的是 new ToastConstructor({ data: options })
中的 data 就是 main.vue 組件中的 data,不是隨隨便便取的字段名,傳入的 options
會和組件中的 data 合併(Vue 的功勞)。
em。。。是的,就這麼簡單,如今讓咱們繼續來調用一下它:
<script>
export default {
methods: {
showToast() {
this.$toast({
content: "哈哈哈哈,消失的賊快",
duration: 500
});
}
}
};
</script>
複製代碼
運行一下就能夠看到:
這裏咱們打算支持 this.$toast.error()
和 this.$toast.success()
這兩種方式,因此咱們第一步仍是要先去修改一下 main.vue 文件的內容(主要就是根據 type
值來修改組件的樣式),就像下面這樣:
<!--main.vue-->
<template>
<div class="toast" :class="type ? `toast--${type}` : ''">
<p>{{ content }}</p>
</div>
</template>
<script> export default { ... data() { return { type: "", content: "", duration: 3000 }; }, ... }; </script>
<style lang="scss" scoped> .toast { ... &--error p { background: rgba(255, 0, 0, 0.5); } &--success p { background: rgba(0, 255, 0, 0.5); } } </style>
複製代碼
其次,this.$toast.error()
其實就等價於 Toast.error()
,因此咱們如今的目的就是要給 Toast 函數擴充方法,也比較簡單,就先看代碼再解釋吧:
// main.js
const Toast = function(options = {}) {
...
};
// 如下就是在 Toast 函數中拓展 ["success", "error"] 這兩個方法
["success", "error"].forEach(type => {
Toast[type] = options => {
options.type = type;
return Toast(options);
};
});
export default Toast;
複製代碼
咱們能夠看到 Toast.error()
和 Toast.success()
最終仍是調用 Toast(options)
這個函數,只不過在調用以前須要多作一步處理,就是將 ["success", "error"]
做爲一個 type
參數給合併進 options
裏面再傳遞,僅此而已😬。
那就試試效果吧:
<script> export default { methods: { showToast() { this.$toast({ content: "這是正常的" }); }, showErrorToast() { this.$toast.error({ content: "居然失敗了" }); }, showSuccessToast() { this.$toast.success({ content: "竟然成功了" }); } } }; </script>
複製代碼
至此,一個經過 js 調用的簡單 toast 組件就搞定啦,短短的幾行代碼仍是挺考驗 js 功底的💪。固然這只是個超簡易版的 demo,顯然不夠完善和健壯,因此咱們能夠在此基礎上擴充一下,好比當 duration <= 0
的時候,咱們讓這個 toast 一直顯示,而後擴展一個 close
方法來關閉等等之類的。不過仍是那句老話,實踐纔是檢驗真理的惟一標準。紙上得來終覺淺,絕知此事要躬行。step by step, day day up ! 🎉 🎉 🎉
最後的最後,安利一下對本文有幫助的兩篇文章:
一、原來 Element 的組件源碼還能這麼看
二、基於 vue-cli3 打造屬於本身的 UI 庫