vue ElementUI message组件思路解读

601 阅读1分钟

2020/12/13 继续搞 不能三天打鱼两条晒网哦

message 基础组件

<template>
  <!-- 离开时候触发事件 -->
  <transition name="el-message-fade" @after-leave="handleAfterLeave">
    <!-- 样式控制台  鼠标移入移出事件 移除清除定时器让 message持续显示 移出重新计时  -->
    <div
      :class="[
        'el-message',
        type && !iconClass ? `el-message--${ type }` : '',
        center ? 'is-center' : '',
        showClose ? 'is-closable' : '',
        customClass
      ]"
      :style="positionStyle"
      v-show="visible"
      @mouseenter="clearTimer"
      @mouseleave="startTimer"
      role="alert">
      <i :class="iconClass" v-if="iconClass"></i>
      <i :class="typeClass" v-else></i>
      <slot>
        <p v-if="!dangerouslyUseHTMLString" class="el-message__content">{{ message }}</p>
        <p v-else v-html="message" class="el-message__content"></p>
      </slot>
      <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,
      type: 'info',
      iconClass: '',
      customClass: '',
      onClose: null,
      showClose: false,
      closed: false,
      verticalOffset: 20,
      timer: null,
      dangerouslyUseHTMLString: false,
      center: false,
    };
  },

  computed: {
    typeClass() {
      return this.type && !this.iconClass
        ? `el-message__icon el-icon-${typeMap[this.type]}`
        : '';
    },
    // 控制顶部的距离
    positionStyle() {
      return {
        top: `${this.verticalOffset}px`,
      };
    },
  },

  watch: {
    closed(newVal) {
      // 如果closed 是true的话  控制visible 让元素消失
      if (newVal) {
        this.visible = false;
      }
    },
  },

  methods: {
    // v-show 动画离开后的事件
    handleAfterLeave() {
      // 完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。
      // 触发 beforeDestroy 和 destroyed 的钩子。
      this.$destroy(true);
      // 删除dom
      this.$el.parentNode.removeChild(this.$el);
    },
    close() {
      this.closed = true;
      if (typeof this.onClose === 'function') {
        this.onClose(this);
      }
    },

    clearTimer() {
      clearTimeout(this.timer);
    },
    // message 出来多久之后制动消失
    startTimer() {
      if (this.duration > 0) {
        this.timer = setTimeout(() => {
          if (!this.closed) {
            this.close();
          }
        }, this.duration);
      }
    },
    keydown(e) {
      if (e.keyCode === 27) { // esc关闭消息
        if (!this.closed) {
          // 这边关闭的时候按道理应该也要情况定时器  可能时候大意了
          this.close();
        }
      }
    },
  },
  mounted() {
    this.startTimer();
    // 监听 键盘ecs 关闭
    document.addEventListener('keydown', this.keydown);
  },
  beforeDestroy() {
    document.removeEventListener('keydown', this.keydown);
  },
};
</script>

API 调用

import Vue from 'vue';
import { PopupManager } from '@/utils/popup';
import { isVNode } from '@/utils/vdom';
import Main from './main.vue';

// 继承基础的组件 返回一个组件构造函数
const MessageConstructor = Vue.extend(Main);

let instance;
// 这边储存实例
const instances = [];
// id标识作用
let seed = 1;

// 入口
const Message = function (options) {
  // 是否是服务端渲染
  if (Vue.prototype.$isServer) return;
  // 参数处理下
  options = options || {};
  if (typeof options === 'string') {
    options = {
      message: options,
    };
  }
  // 用户传进来的 关闭时候回调
  const userOnClose = options.onClose;
  const id = `message_${seed++}`;
  // 做一层封装  v-show为false关闭的时候会调用这个销毁
  options.onClose = function () {
    // 因为可能会有多message  传进去ID
    Message.close(id, userOnClose);
  };
  // 实例化出一个对象
  instance = new MessageConstructor({
    data: options,
  });
  instance.id = id;
  // 判断 message 是个vNode  针对这种情况
  // this.$message({
  //   message: h('p', null, [
  //     h('span', null, '内容可以是 '),
  //     h('i', { style: 'color: teal' }, 'VNode')
  //   ])
  // });
  // function isVNode(node) {
  //   return node !== null && typeof node === 'object' && hasOwn(node, 'componentOptions');
  // }
  if (isVNode(instance.message)) {
    // 把这个赋值给默认的slot
    instance.$slots.default = [instance.message];
    instance.message = null;
  }
  // 挂载这个实例 插入到body
  instance.$mount();
  document.body.appendChild(instance.$el);
  // 这边设置实例距离顶部的距离
  let verticalOffset = options.offset || 20;
  instances.forEach((item) => {
    verticalOffset += item.$el.offsetHeight + 16;
  });
  instance.verticalOffset = verticalOffset;
  // 显示   设置上层级   把实例存到instance
  instance.visible = true;
  // PopupManager这个是ele组件库维护层级的一个类 下次解读对话框在分析下
  instance.$el.style.zIndex = PopupManager.nextZIndex();
  instances.push(instance);
  return instance;
};

// 设置上一些简单的调用
// this.$message.error(...) 等等  相当于设置 options 对应的type
['success', 'warning', 'info', 'error'].forEach((type) => {
  Message[type] = (options) => {
    if (typeof options === 'string') {
      options = {
        message: options,
      };
    }
    options.type = type;
    return Message(options);
  };
});

//
Message.close = function (id, userOnClose) {
  const len = instances.length;
  let index = -1;
  let removedHeight;
  // 找到这个实例
  for (let i = 0; i < len; i++) {
    if (id === instances[i].id) {
      // 当前这个 message组件的高度
      removedHeight = instances[i].$el.offsetHeight;
      index = i;
      // 有回调的话执行
      if (typeof userOnClose === 'function') {
        userOnClose(instances[i]);
      }
      // 删除这个实例
      instances.splice(i, 1);
      break;
    }
  }
  // 如果这个删除的这个实例是最后一个 那么就不必调整下面实例的高度了
  if (len <= 1 || index === -1 || index > instances.length - 1) return;
  // 比删除的这个实例后面加进来的 调整下距离顶部的距离
  for (let i = index; i < len - 1; i++) {
    const dom = instances[i].$el;
    // 相当于往上面移一位
    dom.style.top = `${parseInt(dom.style.top, 10) - removedHeight - 16}px`;
  }
};

Message.closeAll = function () {
  // 从后面往前面删  应该也是为了省事哦
  for (let i = instances.length - 1; i >= 0; i--) {
    instances[i].close();
  }
};
// 下次分析下dialog
export default Message;