this.$toast() 瞭解一下?

尤水就下也發表於2019-04-18

前言

在平時的開發過程中,我們總是先寫好一個元件,然後在需要的頁面中用 import 引入即可,但如果是下面這種型別的元件呢?

this.$toast() 瞭解一下?
上面這種型別的浮層提示有一個很大的特點,就是使用頻率特別高,幾乎每個頁面都會用到它,於是乎我們就要在每個頁面中去引入該元件,並且在每個頁面都得通過一個變數來控制它的顯隱,這顯然不是我們想要的?。。。那我們想要的是什麼樣呢??用過一些 UI 框架的同學們應該知道有這樣一種用法:

this.$toast({
    duration: 3000,
    content: '這是一條訊息提示'
});
複製程式碼

沒錯,就是這麼簡單的一句話就萬事大吉了(就是用 js 呼叫元件而已啦?)。那這種效果究竟是怎麼實現的呢?今天就讓我們來(手把手? )一探究竟吧!

前置知識

不知道小夥伴們有沒有用過 Vue.extend() 這個東東,反正我是很少碰過,印象不深,所以這裡我們先來短暫瞭解一下 Vue.extend() 主要是用來幹嘛的。先來個官方說明(不多的,堅持下):

this.$toast() 瞭解一下?
沒怎麼看懂??沒關係,不重要,你只要記住(加少許理解)以下用法即可:

// 匯入以往的普通元件
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 也是一樣的)。這裡就直接上程式碼啦,畢竟它的結構簡單到爆了,也不是本章節的重點:

<!-- 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>
複製程式碼

上面的內容想必大家應該都能看懂,所以這裡就直接講下面的重點了。

寫一個 main.js

我們在 main.vue 的同級目錄下新建一個 main.js 檔案。我們先瞟一眼檔案內容(也不多,已經是個最簡版了)?:

// 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 元件是不會跟著立馬消失的,這點要注意哦?。
這裡順便給個元件的目錄結構,如下圖所示:

this.$toast() 瞭解一下?

開始呼叫

呼叫方式很簡單,首先我們在入口檔案 main.js(和上面不是同一個?) 里加上兩行程式碼,這樣我們就能在需要的地方直接用 js 呼叫它了,如下圖所示:

this.$toast() 瞭解一下?
然後在頁面中測試一下,就像下面這樣子:
this.$toast() 瞭解一下?
執行一下程式碼:
this.$toast() 瞭解一下?
嗯,挺好,小有成就的 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() 瞭解一下?
當然,這還沒完,我們繼續新增個小功能點?。。。

支援 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>
複製程式碼

this.$toast() 瞭解一下?
大讚無疆,大。贊。。無。。。疆。。。。。?

結語

至此,一個通過 js 呼叫的簡單 toast 元件就搞定啦,短短的幾行程式碼還是挺考驗 js 功底的?。當然這只是個超簡易版的 demo,顯然不夠完善和健壯,所以我們可以在此基礎上擴充一下,比如當 duration <= 0 的時候,我們讓這個 toast 一直顯示,然後擴充套件一個 close 方法來關閉等等之類的。不過還是那句老話,實踐才是檢驗真理的唯一標準。紙上得來終覺淺,絕知此事要躬行。step by step, day day up ! ? ? ?
最後的最後,安利一下對本文有幫助的兩篇文章:
1、原來 Element 的元件原始碼還能這麼看
2、基於 vue-cli3 打造屬於自己的 UI 庫

相關文章