element-ui Message組件源碼分析整理筆記(八)

Message組件源碼:javascript

main.js

import Vue from 'vue';
import Main from './main.vue';
import { PopupManager } from 'element-ui/src/utils/popup';
import { isVNode } from 'element-ui/src/utils/vdom';
let MessageConstructor = Vue.extend(Main);

let instance;
let instances = []; // 存放當前未close的message
let seed = 1;

const Message = function(options) {
  // 當前 Vue 實例是否運行於服務器
  if (Vue.prototype.$isServer) return;
  options = options || {};
  if (typeof options === 'string') {
    options = {
      message: options
    };
  }
  //userOnClose用來存放用戶設置關閉時的回調函數, 參數爲被關閉的 message 實例
  let userOnClose = options.onClose;
  let id = 'message_' + seed++;

  // 重寫options.onClose
  options.onClose = function() {
    Message.close(id, userOnClose);
  };
   // 建立message實例,此時數據尚未掛載呢,$el 屬性目前不可見,沒法訪問到數據和真實的dom
  instance = new MessageConstructor({
    data: options
  });
  instance.id = id;

  //判斷instance.message是否是虛擬節點
  if (isVNode(instance.message)) {
    instance.$slots.default = [instance.message];
    instance.message = null;
  }
   //手動地掛載一個未掛載的實例。$mount(param)中param不存在時,模板將被渲染爲文檔以外的的元素,而且你必須使用原生 DOM API 把它插入文檔中。
  instance.vm = instance.$mount();
  //用原生DOM API把它插入body中
  document.body.appendChild(instance.vm.$el);
  instance.vm.visible = true;
  instance.dom = instance.vm.$el;
  // css z-index層級疊加,覆蓋以前已出現但還未close的message
  instance.dom.style.zIndex = PopupManager.nextZIndex();
  instances.push(instance);
  return instance.vm;
};

// 給Message增長四個直接調用的方法
// 支持this.$message.success('xxx')方式調用,等同於this.$message({type: 'success',message: 'xxx'})
['success', 'warning', 'info', 'error'].forEach(type => {
  Message[type] = options => {
    if (typeof options === 'string') {
      options = {
        message: options
      };
    }
    options.type = type;
    return Message(options);
  };
});

// 組件的close方法中調用onClose再調該方法
Message.close = function(id, userOnClose) {
  for (let i = 0, len = instances.length; i < len; i++) {
    if (id === instances[i].id) { // 經過id找到該message實例
      if (typeof userOnClose === 'function') {
        userOnClose(instances[i]);
      }
      instances.splice(i, 1);  // 移除message實例
      break;
    }
  }
};
//關閉全部的消息提示彈窗
Message.closeAll = function() {
  for (let i = instances.length - 1; i >= 0; i--) {
    instances[i].close();
  }
};

export default Message;

main.vue

<template>
  <transition name="el-message-fade">
    <div
      :class="[
        'el-message',
        type && !iconClass ? `el-message--${ type }` : '',
        center ? 'is-center' : '',
        showClose ? 'is-closable' : '',
        customClass
      ]"
      v-show="visible"
      @mouseenter="clearTimer"
      @mouseleave="startTimer"
      role="alert">
        <!--自定義圖標存在時顯示-->
      <i :class="iconClass" v-if="iconClass"></i>
        <!--自定義圖標不存在時根據type顯示圖標-->
      <i :class="typeClass" v-else></i>
      <slot>
          <!--用戶設置的message的參數爲字符串時,顯示字符串-->
        <p v-if="!dangerouslyUseHTMLString" class="el-message__content">{{ message }}</p>
        <!--用戶設置的message的參數爲VNode時,在此處顯示-->
        <p v-else v-html="message" class="el-message__content"></p>
      </slot>
        <!--當用戶設置的關閉按鈕顯示爲true時,顯示關閉圖標-->
      <i v-if="showClose" class="el-message__closeBtn el-icon-close" @click="close"></i>
    </div>
  </transition>
</template>

<script type="text/babel">
  const typeMap = {
    success: 'success',
    info: 'info',
    warning: 'warning',
    error: 'error'
  };

  export default {
    data() {
      return {
        visible: false,
        message: '', //消息文字
        duration: 3000, //顯示時間, 毫秒。設爲 0 則不會自動關閉
        type: 'info',
        iconClass: '', //自定義圖標的類名,會覆蓋 type
        customClass: '', //自定義類名
        onClose: null,
        showClose: false, //是否顯示關閉按鈕
        closed: false, //用來判斷消息提示彈窗是否關閉
        timer: null,
        dangerouslyUseHTMLString: false, //是否將 message 屬性做爲 HTML 片斷處理
        center: false
      };
    },

    computed: {
      // 根據type返回對應的圖標類名
      typeClass() {
        return this.type && !this.iconClass
          ? `el-message__icon el-icon-${ typeMap[this.type] }`
          : '';
      }
    },

    watch: {
      closed(newVal) {
        if (newVal) {
          this.visible = false;
          //transitionend事件在 CSS 完成過渡後觸發。
          this.$el.addEventListener('transitionend', this.destroyElement);
        }
      }
    },

    methods: {
      destroyElement() {
        this.$el.removeEventListener('transitionend', this.destroyElement);
        //徹底銷燬一個實例。清理它與其它實例的鏈接,解綁它的所有指令及事件監聽器。
        // 在vue v1.x中$destroy(true)的參數爲true時,則從DOM中刪除其關聯的DOM元素或片斷;在vue2.0中不須要加參數
        this.$destroy(true);
        this.$el.parentNode.removeChild(this.$el);
      },

      close() {
        this.closed = true;
        if (typeof this.onClose === 'function') {
          this.onClose(this);
        }
      },
      //鼠標進入消息提示彈窗時,定時器清空,彈窗一直顯示
      clearTimer() {
        clearTimeout(this.timer);
      },
      // 鼠標離開消息提示彈窗時,設置定時器,彈窗在this.duration關閉
      startTimer() {
        if (this.duration > 0) {
          this.timer = setTimeout(() => {
            if (!this.closed) {
              this.close();
            }
          }, this.duration);
        }
      },
      // esc關閉消息
      keydown(e) {
        if (e.keyCode === 27) {
          if (!this.closed) {
            this.close();
          }
        }
      }
    },
    mounted() {
      this.startTimer();
      document.addEventListener('keydown', this.keydown);
    },
    beforeDestroy() {
      document.removeEventListener('keydown', this.keydown);
    }
  };
</script>
相關文章
相關標籤/搜索