Vue.js 實踐(3):實現一個漂亮、靈活、可複用的提示組件

此次的教程裏,咱們要把組件化進行到底!最近半年的幾個項目中,都遇到了須要使用Toast或者Notification組件的狀況。在目前已有的一些基於Vue.js開發的組件庫,都沒有找到太合適的,因此本身重頭實現了一個。歷經幾個項目的磨練,這個提示組件的功能已經愈來愈完善,此次就分享一下組件以及其實現思路吧。css

GitHub 倉庫:https://github.com/Yuyz0112/vue-notie
Demo 地址:http://lab.myriptide.com/vue-notie/vue

yummy-notie-m.gif

深刻組件化,組件的拆分、整合與複用

Vue.js的組件化能夠說是其招牌特性之一,而在實際應用時,並不是一味地追求組件顆粒越小越好,而是須要根據項目的實際需求,來分析本身須要什麼級別的組件。git

例如在一個SPA中,我可能有主頁、文章列表頁、文章頁、我的中心頁4個主要的視圖,因而我將其分別對應的寫成4個組件。github

可是在實際編寫的過程當中,發現他們共用了同一套側邊欄,而側邊欄對應的代碼也在4個組件中重複書寫了4次。因此能夠將側邊欄單獨寫成一個組件進行復用。web

以後,咱們可能發現能夠複用的還有一些表單、按鈕之類的內容咱們均可以複用成組件。但實際上,咱們也會發現過分的組件化會致使代碼量上升、開發時間增長以及額外的數據傳遞等等。因此若是不打算製做一個完整的組件庫,那麼在實際項目中作到按需拆分、整合便可,不用過度的追求每一個可複用的部分都寫成單個組件。vuex

爲何須要一個提示組件

由於alert大部分時間不能知足咱們的需求啊。每每項目裏須要一個相似於alert的東西,用美觀、可定製的方式提示用戶一些信息,所以這樣一個提示組件頗有必要。數組

同時,咱們也不但願同一時間出現多個提示混淆用戶,所以在設計上,咱們將提示組件設定爲具備惟一性,整個應用中各個視圖調用的都是同一個提示組件。sass

Show me the code

接下來,由簡入繁依次實現提示組件的各個功能。app

基本功能

最基本的功能固然是觸發後顯示,而且可以以某種方式關閉。惟一須要自定義的部分,就是具體顯示的內容。因此最開始組件長這樣:ide

<template>
  <div class="notification fixed"
  v-if="show"
  transition="slide">
    <div class="delete"
    @click="close()"></div>
    <div class="content">
      {{ options.content }}
    </div>
  </div>
</template>

<script>
export default {
  props: {
    options: {
      type: Object,
      default: () => {
        return {}
      }
    },
    show: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    close () {
      this.show = false
      this.options = {}
    }
  }
}
</script>

<style scoped lang="sass">
  .slide-transition
    transition: all .3s ease
    transform: translate3d(0, 0, 0)
  .slide-enter,
  .slide-leave
    transform: translate3d(0, -100%, 0)
  .delete
    -moz-appearance: none
    -webkit-appearance: none
    background: rgba(51,51,51,0.2)
    cursor: pointer
    display: inline-block
    height: 24px
    position: relative
    vertical-align: top
    width: 24px
    float: right
    &:before,
    &:after
      background: #fff
      content: ""
      display: block
      height: 2px
      left: 50%
      margin-left: -25%
      margin-top: -1px
      position: absolute
      top: 50%
      width: 50%
    &:before
      transform: rotate(45deg)
    &:after
      transform: rotate(-45deg)
    &:hover
      background: rgba(51,51,51,0.5)
  .notification
    width: 100%
    line-height: 2
    z-index: 3
    position: fixed
    top: 0
    left: 0
    .content
      padding: .75rem 2rem
</style>

思路很簡單,props傳遞兩個數據,show用於控制顯示,options傳入包括內容在內的自定義內容。爲了讓提示的顯示更加天然,添加了一個滑動進入和離開的transition。

注意:這裏的關閉按鈕是經過css實現的,若是在你的項目中有對應的icon,能夠將其替換掉。

在此處,也可使用slot來進行內容的傳遞,但考慮到以後還有別的參數須要傳遞至組件內,一次用一個統一的對象options進行傳遞。

自定義樣式

一般提示的內容種類不少,有的是成功提示,有的是警告,有的則是報錯。所以咱們須要定義不一樣的樣式以表達不一樣的內容。
方法很簡單,在options中傳入背景色和文字顏色兩個參數,若是組件中檢測到了傳入的樣式參數,就用其替換默認樣式。

Vue.js在處理動態樣式時很是靈活,爲了讓代碼更清晰,我沒有選擇將動態樣式內聯,而是單獨使用一個計算屬性setStyle進行設定:

computed: {
  setStyle () {
    return {
      color: this.options.textColor || '#fff',
      background: this.options.backgroundColor || '#21e7b6'
    }
  }
}

這樣一來,只要在options中一併傳入textColor和backgroundColor兩個屬性,就能夠輕鬆自定義提示樣式了。

自動關閉

不少時候,咱們但願提示在必定時間以後能夠自動關閉,所以組件也須要擴展出一個自動關閉的模式。一樣的,在「數據驅動」的思想下,咱們應該提供一個數據,用來代表這個提示是否自動關閉。

options中的autoClose屬性就是這個做用。一樣的,自動關閉的延遲時間顯然也要可以自定義,所以還一同添加了showTime這一屬性。

自動關閉自己不太複雜,咱們只須要使用setTimeout,定義一個計時器便可。

首先是監聽提示組件的顯示。

在這裏,我經過watch監聽options的變化來處罰計時器。因爲咱們已經定義了一個close方法用於關閉計時器,而且在關閉時重置了show和options的值,因此在options變化時,只須要判斷options中的autoClose是否爲true,就能知道是否須要啓動計時器了。這裏單獨使用一個countdown方法來處理定時器相關的操做。

新增代碼以下:

data () {
  return {
    timers: []
  } 
},
methods: {
  countdown () {
    if (this.options.autoClose) {
      const t = setTimeout(() => {
        this.close()
      }, this.options.showTime || 3000)
      this.timers.push(t)
    }
  }
},
watch: {
  options () {
    this.timers.forEach((timer) => {
      window.clearTimeout(timer)
    })
    this.timers = []
    this.countdown()
  }
}

細心地你確定會發現,這段代碼中,有一些奇怪的處理。咱們定義了一個空數組timers,而且每次開始一個計時器的時候,就把計時器存入數組中,而每次options變化時,咱們也從timers中遍歷全部計時器並取消,以後清空timers。

這個作法,主要是爲了不一個計時器尚未結束時,又開始一個新的提示所引起的提示被提早關閉的清空。舉個例子,若是沒有這樣的處理,那麼先發出一個自動關閉的提示,在其沒自動關閉以前,就再發出一個新的提示。那麼第一個提示的定時器依然會錯誤的關閉新提示。

這樣的問題主要是因爲咱們全部的計時器都是在同一個組件中,本質上都是同一個提示,所以須要清除計時器,避免衝突。許多組件庫中相似的功能組件,是採用每一條提示就新生成一個提示組件的方式來實現的。可是那樣在多個提示連續出現時,就會出現堆疊在一塊兒,又各自離開的狀況。

以前的版本中,個人提示組件也採用了相似的設計方式,可是在最近的一個項目中,須要實現半透明的提示組件,就出現了堆疊後看不清提示文字的現象,才使用瞭如今新的模式。

進一步擴展

緊接着,我拓展了一個自動關閉模式下的倒計時條功能。思路上沒有使用Vue.js的transition系統,而是採用了Css3自己的動畫系統。在一個自動關閉的提示被初始化時,爲計時條添加一個樣式,效果是向X軸負方向移動100%,transition時間則經過計算屬性對應設定。具體實現能夠參考源代碼,這裏很少作贅述。

加強靈活性

最後則是讓提示組件更靈活。有的時候,咱們想展現的多是能夠自定義樣式的文本、亦或是一個超連接甚至更多。而Vue.js實現起來不要太簡單。咱們只須要將組件中用於渲染的{{ options.content }}變爲{{{ options.content }}}便可,對於3重花括號的模板,Vue.js會將其中的HTML標籤按照正常內容渲染。

如此一來,咱們就能夠將任何HTML內容放入提示中了。固然必定要注意避免將用戶輸入的內容渲染到3重花括號的模板中,避免XSS攻擊。

結合vuex

不少時候,咱們會把提示組件引入到App.vue這個根組件中,可是發出提示的多是組件樹中的任何一個組件。若是不想代碼中遍及各類dispatch和broadcast,那麼引入vuex來進行管理是個很好的方案。

大體的思路以下:

// store.js
const state = {
  show: false,
  options: {
    autoClose: false,
    content: 'notice content'
  }
}

const mutations = {
  NEW_NOTICE (state, options) {
    state.show = true
    state.options = options
  },
  CLOSE_NOTICE (state) {
    state.show = false
    state.options = {}
  }
}

// actions.js

export const newNotice = ({dispatch}, options) => {
  dispatch('NEW_NOTICE', options)
}
export const closeNotice = ({dispatch}) => {
  dispatch('CLOSE_NOTICE')
}

// Notification.vue

vuex: {
  getters: {
    show: state => state.show
    options: state => state.options
  },
  actions: {
    close: closeNotice
  }
}

// 任意調用notice的組件

vuex: {
  actions: {
    notice: newNotice
  }
}

引入vuex後,按上述代碼進行配置,就能夠在任意一處組件中,使用this.notice({options})傳遞數據。不過因爲vuex的單項數據流動特性,全部對state數據的操做都必須通過actions調用mutations實現,包括提示組件中的close方法也要替換成actions中的closeNotice方法。

綜述

經過這個提示組件,咱們更熟練的掌握了Vue.js的組件系統、數據傳遞、計算屬性、transition動畫等特性。另外此組件已經能夠直接用於生產環境中,歡迎star、fork、pr。

相關文章
相關標籤/搜索