從0開始手寫一個(gè)Vue消息彈窗組件

前言

近期閑來無事,就想折騰一點(diǎn)東西,無奈技術(shù)水平太低,做不了什么高深復(fù)雜的項(xiàng)目,所以還是先從簡單的做起吧。平時(shí)我們做項(xiàng)目的時(shí)候或多或少都會(huì)用到一些組件庫,比如element、muse之類的,所以這次我想自己手動(dòng)寫一個(gè)組件,封裝成一個(gè)包,可以暴露到外部去調(diào)用。

效果預(yù)覽

廢話不多說,這次寫的是一個(gè)Vue的消息彈窗組建,借鑒了element的message組件,大家可以去看一看element的源碼,里面一些組件的實(shí)現(xiàn)方式很巧妙,非常值得去學(xué)習(xí)。話不多說,先來看看最終的實(shí)現(xiàn)效果:

不同主題

關(guān)閉按鈕
不同位置

代碼

第一步應(yīng)該是寫一個(gè)模板:

<template>
  <transition name="msg-fade" @after-leave="afterLeave">
    <div :class="classes" class="msg-container" v-show="visible" role="alert" @mouseenter="clearTimer" @mouseleave="startTimer">
      <span>{{ message }}</span>
      <i class="msg-icon-wrapper" v-if="showCloseButton" @click="close">
        <img class="msg-icon" src="./close.svg" alt="close" />
      </i>
    </div>
  </transition>
</template>

可以看到,html內(nèi)容是很簡單的,主要是幾個(gè)標(biāo)簽和一些props,還有幾個(gè)事件,然后我們?cè)賮砜磈s:

<script>
export default {
  name: 'Message',
  props: {
    // 要顯示的消息
    message: {
      type: String,
      require: true
    },
    // 主題
    type: {
      type: String,
      default: 'normal'
      // normal success error warning
    },
    // 是否顯示關(guān)閉按鈕
    showCloseButton: {
      type: Boolean,
      default: false
    },
    // 組件出現(xiàn)的位置
    location: {
      type: String,
      default: 'top-center'
      // top-center top-right top-left bottom-center bottom-right bottom-left
    },
    // 自定義樣式名
    className: {
      type: String,
      default: ''
    },
    // 持續(xù)時(shí)間
    duration: {
      type: Number,
      default: 2000
    }
  },
  data () {
    return {
      // 動(dòng)態(tài)修改樣式
      classes: [
        {
          'msg-icon-padding': this.showCloseButton
        }, 
        `msg-${this.type}`,
        `msg-${this.location}`,
        this.className
      ],
       // 控制組件顯示
      visible: false,
       // 是否關(guān)閉
      closed: false,
       // 定時(shí)器
      timer: null
   }
  },
  watch: {
    closed (newVal) {
      if (newVal) {
        this.visible = false
      }
    }
  },
  methods: {
    close () {
      this.closed = true
    },
    afterLeave () {
      this.$destroy(true)
      this.$el.parentNode.removeChild(this.$el)
    },
    clearTimer () {
      clearTimeout(this.timer)
    },
    startTimer () {
      const timeout = this.duration
      if (timeout > 0) {
        this.timer = setTimeout(() => {
          if (!this.closed) {
            this.close()
          }
        }, timeout);
      }
    }
  },
  mounted () {
    this.startTimer()
    this.visible = true
  },
  beforeDestroy () {
    this.clearTimer()
  }
}
</script>

可以看到組件的代碼邏輯也是非常簡單的,props的處理我就不多細(xì)說了,可能每一個(gè)人的需求都一樣。重點(diǎn)要講一講的是組件的創(chuàng)建和銷毀過程。在組件掛載后,組件會(huì)調(diào)用startTimer方法生成一個(gè)定時(shí)器,按照duration指定的時(shí)間后調(diào)用close方法,close方法會(huì)設(shè)定data的closed為true,visible監(jiān)聽到closed變?yōu)閠rue后就變?yōu)閒alse,所以根據(jù)v-show這個(gè)指令,組件的display樣式屬性就會(huì)變成none。最后Vue官方內(nèi)置的過渡組件transition會(huì)調(diào)用一系列鉤子函數(shù),我們直接在afterLeave這個(gè)鉤子上銷毀組件和清除dom。看到這里,可能有同學(xué)會(huì)有疑問了?為啥要這樣銷毀組件啊,直接用一個(gè)prop控制組件顯示不就可以了嗎?其實(shí)對(duì)于一般的組件我們是沒必要弄成這樣,但是我們得思考一下消息彈窗的使用場(chǎng)景啊。一般情況下,消息彈窗都是在完成了某種操作后,彈出來告訴用戶操作成功與否,所以我們通常用js控制消息彈窗顯示。因此,我們的消息彈窗組件不僅僅是聲明式的,更應(yīng)該是命令式的,直接用js控制顯示與否。

很多同學(xué)應(yīng)該都使用過element,element在全局引入的時(shí)候會(huì)向vue上掛載一個(gè)message的方法,一般我們都是this.$message這樣來操作的,我的目標(biāo)也是做成這樣子。

import Vue from 'vue'
import Message from './Message.vue'

const messageConstructor = Vue.extend(Message)
let count = 1

const notify = (options = {}) => {
  // 如果Vue運(yùn)行在服務(wù)端,放棄調(diào)用
  if (Vue.prototype.$isServer) {
    return
  }
  
  if (typeof options === 'string') {
    options = {
      message: options
    }
  }

  // 創(chuàng)造實(shí)例
  const instance = new messageConstructor({
    propsData: options
  })

  // 渲染并掛載到body上
  const vm = instance.$mount()
  document.body.appendChild(vm.$el)
  vm.$el.style.zIndex = count

  // 返回一個(gè)主動(dòng)關(guān)閉的方法
  const close = () => {
    vm.closed = true
  }
  return close
}

export default notify

這里我們用了Vue的一個(gè)方法,vue.extend,這個(gè)方法接受一些配置參數(shù),返回一個(gè)可以生產(chǎn)vue實(shí)例的構(gòu)造函數(shù),其實(shí)我們平時(shí)使用的組件一般都是這些構(gòu)造函數(shù)實(shí)例化的對(duì)象。代碼的邏輯很簡單,當(dāng)notify方法被調(diào)用的時(shí)候會(huì)創(chuàng)造一個(gè)message組件的實(shí)例對(duì)象,同時(shí)傳入props,最后渲染成dom并掛載到document.body上。最后我們提供一個(gè)install方法將notify和message組件全局安裝到Vue中:

class Plugin {
  static install (Vue) {
    Vue.prototype.notify = notify
    Vue.mixin({
      components: {
        'oce-message': Message
      }
    })
  }
}
export default Plugin


// 全局注冊(cè)
import oce-message from 'oce-message'
import Vue from 'vue'

Vue.use(oce-message)

// 使用
<oce-message ... />
this.notify(...)

好了,一個(gè)簡單的消息彈窗組件就完成了,具體的代碼和使用方法在這里oce-message可以找到,歡迎star和提issue。

后續(xù)

這個(gè)組件有點(diǎn)簡陋,其實(shí)有一些可以優(yōu)化的地方:

  1. 樣式
  2. 過渡動(dòng)畫
  3. 性能(實(shí)例是否可以做成一個(gè)單例?)

??????
我是naecoo,前端打雜工程師,偶爾寫寫灌水文章...
Github
博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容