this.$toast() 瞭解一下?

前言

在平時的開發過程當中,咱們老是先寫好一個組件,而後在須要的頁面中用 import 引入便可,但若是是下面這種類型的組件呢👇 javascript

上面這種類型的浮層提示有一個很大的特色,就是使用頻率特別高,幾乎每一個頁面都會用到它,因而乎咱們就要在每一個頁面中去引入該組件,而且在每一個頁面都得經過一個變量來控制它的顯隱,這顯然不是咱們想要的🙅。。。那咱們想要的是什麼樣呢🤔?用過一些 UI 框架的同窗們應該知道有這樣一種用法:

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 調用、渲染並掛載組件的精髓所在,至關於早前的 createElementappendChild,有殊途同歸之效。這個點須要咱們好好熟悉一下,因此你能夠先停下來屢屢思路🤔。
補充一下🤐:$mount() 裏面若是沒有參數,說明組件只是渲染了但尚未掛載到頁面上,若是有正確的(元素)參數則直接掛載到元素下面。前端

寫一個 toast 組件

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.js

咱們在 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

而後在頁面中測試一下,就像下面這樣子:
運行一下代碼:
嗯,挺好,小有成就的 feel 👏👏👏。

支持可傳參數

別急,咱們好像還漏了點什麼🤔。。。對了,如今還不支持傳參呢,直接調用 this.$toast() 就只能顯示————服務器錯誤,請稍後重試(這下全都是後端的鍋了😊)。但咱們但是個有追求的前端,不能侷限於此,因此如今讓咱們來嘗試增長下兩個可配置參數,這裏拿 durationcontent 舉個栗子🌰。
首先咱們要修改 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.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 庫

相關文章
相關標籤/搜索