設計模式在vue中的應用(五)

hailx發表於2019-02-17

前言

目錄整理:
設計模式在vue中的應用(一)
設計模式在vue中的應用(二)
設計模式在vue中的應用(三)
設計模式在vue中的應用(四)
設計模式在vue中的應用(五)
設計模式在vue中的應用(六)
設計模式在vue中的應用(七)

為什麼要寫這些文章呢。正如設計模式(Design Pattern)是一套被反覆使用、多數人知曉的、經過分類的、程式碼設計經驗的總結(來自百度百科)一樣,也是想通過分享一些工作中的積累與大家探討設計模式的魅力所在。
在這個系列文章中為了輔助說明引入的應用場景都是工作中真實的應用場景,當然無法覆蓋全面,但以此類推也覆蓋到了常見的業務場景



這一篇要講的可能和之前有點區別,前面幾篇要達到我們的目的不得不造出很多物件(元件),而本文的主角是讓我們減少物件——享元模式。
定義(來自網路):

享元模式使用共享技術實現相同或者相似物件的重用。也就是說實現相同或者相似物件的程式碼共享。

使用場景(來自百度百科):

如果一個應用程式使用了大量的物件,而這些物件造成了很大的儲存開銷的時候就可以考慮是否可以使用享元模式。

一、需求

截圖來自iView官方文件(Message元件)

設計模式在vue中的應用(五)

Message元件相信大家不會陌生,不知道大家有沒有親自實現過

二、需求分析

Message元件有以下幾個特點:

  • 互動方式一樣
  • 有三種型別:successwarningerror,對應三種不用的頁面效果:提示icon、背景樣式、字型樣式
  • 接收一段提示文字

可以知道:

互動方式——彈出、隱藏,由共享物件所擁有

提示icon、背景樣式、字型樣式提供介面可配置

使用api統一

三、設計實現

常規使用方式this.$Message.success()this.$Message.warning()this.$Message.error()所以我們需要以vue外掛的形式擴充套件vue的prototype

//Message.js 虛擬碼
export default {
  install (Vue) {
    // 擴充套件Vue的`prototype`
    Vue.prototype.$Message = {
      success (text) {
        // 通常我們可能如下操作,每次new一個新的元件物件
        const Dialog = new Vue({
          ...
        })
        document.body.appendChild(Dialog.$el)
      },
      warning (text) {
        // 同上,new一個新的元件物件
        const Dialog = new Vue({
          ...
        })
        document.body.appendChild(Dialog.$el)
      },
      error (text) {
        // 同上,new一個新的元件物件
        const Dialog = new Vue({
          ...
        })
        document.body.appendChild(Dialog.$el)
      }
    }
  }
}
複製程式碼

如上例子所示每次使用Message元件都需new一個Dialog出來,下面我們使用享元模式的思想達到減少元件物件的目的

//Message.js 虛擬碼
export default {
  install (Vue) {
    // 在使用外掛Vue.use(Message)時例項化一個Dialog元件物件
    const Dialog = new Vue({
      data () {
        return {
          icon: '',
          fontStyle: '',
          backgroundStyle: '',
          text: ''
        }
      }
      ...
    })
    
    // 擴充套件Vue的`prototype`
    Vue.prototype.$Message = {
      success (text) {
        // 改變Dialog的data.xx的值觸發Dialog的更新
        Dialog.icon = successIcon
        Dialog.fontStyle = successFontStyle
        Dialog.backgroundStyle = successBackgroundStyle
        Dialog.text = text
        // 獲取Dialog的最新DOM新增到body標籤中
        document.body.appendChild(Dialog.$el)
      },
      warning (text) {
        // 同上
        ...
        document.body.appendChild(Dialog.$el)
      },
      error (text) {
        // 同上
        ...
        document.body.appendChild(Dialog.$el)
      }
    }
  }
}
複製程式碼

四、結果

都說做事是結果導向的,現在看看我們的設計得到了什麼結果

Dialog只會在專案初始化時被new一次,每次使用Message元件通過改變Dialog的狀態獲取元件DOM,其實很容易知道new一個元件的成本要比一個元件的更新成本高很多

與常規的實現方案相比缺點是就算沒使用也會執行new Dialog()並佔用記憶體

五、附完整實現(示例)

如有bug還請見諒隨手寫的

import './index.scss'

let zIndex = 2001;

export default {
  install (Vue) {
    const Dialog = new Vue({
      data () {
        return {
          text: '這是一個提示',
          icon: 'icon-waiting',
          iconColor: '#308AFE',
          background: '#ddd'
        }
      },
      render (h) {
        zIndex++
        const selfStyle = {
          background: this.background,
          zIndex
        }
        return h('div',
          {
            class: 'm-message',
            style: selfStyle
          },
          [
            h('i', {
              style: {marginRight: '8px', color: this.iconColor},
              class: `iconfont ${this.icon}`
            }),
            this.text
          ]
        )
      }
    }).$mount()

    function appendDialog(message, icon, iconColor, bgColor, time = 3) {
      Dialog.text = message
      Dialog.icon = icon
      Dialog.iconColor = iconColor
      Dialog.background = bgColor
      let timer = ''
      let element = document.createElement('div')
      Dialog.$nextTick(() => {
        element = Dialog.$el.cloneNode(true)
        document.body.appendChild(element)
      })
      if(time > 0) {
        timer = setTimeout(() => {
            element.classList.add('outer')
            setTimeout(() => {
              document.body.removeChild(element)
            }, 500);
            clearTimeout(timer)
        }, time * 1000);
      }
    }

    Vue.prototype.$message = {
      tips (message, time) {
        appendDialog(message, 'icon-waiting', '#308AFE', '#ADD8F7', time)
      },
      warning(message, time) {
        appendDialog(message, 'icon-warn', '#FFAF0D', '#FCCCA7', time)
      },
      success(message, time) {
        appendDialog(message, 'icon-success', '#36B37E', '#A7E1C4', time)
      },
      error(message, time) {
        appendDialog(message, 'icon-error', '#E95B5B', '#FFF4F4', time)
      },
      destory() {
        document.querySelectorAll('.m-message').forEach(ele => ele.remove())
      }
    }
  }
}
複製程式碼

六、總結

回想一下在講解講享元模式時大多會例舉的一個場景

有男女衣服各50套,現在要給這些衣服拍照怎麼辦呢?

土豪做法:new 100個模特物件一人穿一套慢慢拍,有錢任性(記憶體佔有率高)
理性做法:new 一個男模特和一個女模特拍完一套換一套接著拍(暴露一個換衣服的介面),
         也沒差,主要是省錢(物件從100個減少為2個)
複製程式碼

熟悉設計模式同學的可能覺得這個場景不太好,我認同你的觀點,不過用來學習享元模式個人覺得還能接受。
Message元件的具體實現方案不拒絕也不推薦本文的方式(哈哈哈~)

更新:發現elemnet-ui的MessageBox元件就是類似的思路傳送門


本文實現同樣適用於react,為什麼文章以vue做題?vue的template讓我們在理解一些概念的時候可能會有點不適應,而react的jsx可以看做就是在寫JavaScript對各種概念實現更靈活
友情提示:設計模式在vue中的應用應該會寫一個系列,喜歡的同學記得關注下

相關文章