Vue API - extend开发message组件

1,816 阅读1分钟

这两天在研究element-ui,于是依瓢画葫芦弄了个简易版message组件。由于组件是通过指令式调用,因此不能在每个页面都引入

主要使用vue.extend来实现组件挂载在#app外面

// 使用方法
this.$message({
  message: '这是一条message消息',
  onClose: () => {
    console.log('callback')
  }
})

Message.vue

// message/Message.vue
<template>
  <transition>
    <div class="message" :style="style" v-if="visible">
      <div class="message-content">{{ message }}</div>
    </div>
  </transition>
</template>

<script>
export default {
  name: 'MizMessage',
  data () {
    return {
      visible: true,  // 控制组件显示
      message: '',  // 组件内容
      duration: 3000,  // 组件存在时长
      closed: false,  // 组件是否已经关闭
      onClose: null,  // 组件关闭时的回调函数
      verticalOffset: 0  // 每个组件的偏移量
    }
  },
  computed: {
    style () {
      return {
        'top': this.verticalOffset + 'px'
      }
    }
  },
  methods: {
    startTimer () {  // 组件一旦挂载就开始倒计时销毁
      // 如果duration小于等于0,组件不会自动关闭,可以通过点击x号来手动关闭
      if (this.duration > 0 && !this.closed) {
        this.timer = setTimeout(() => {
          this.close()  // 执行关闭方法
        }, this.duration)
      }
    },
    close () {
      this.visible = false  // 设置组件显示
      this.closed = true  // 设置组件已经成功关闭状态
      if (typeof this.onClose === 'function') {
        this.onClose(this)  // 执行关闭回调
      }
    }
  },
  mounted () {
    this.startTimer()  // 挂载即开始执行倒计时
  }
}
</script>
// message/index.js
import Vue from 'vue'
import MessageComponent from './Message.vue'

let seed = 1  // 通过seed++来给每个实例创建不同id
let instances = []  // 用于存放所有组件实例
let MessageConstructor = Vue.extend(MessageComponent)

let Message = (options = {}) => {
  // 当调用直接传入字符串的时候,this.$message('内容')
  if (typeof options === 'string') {
    options = {
      message: options
    }
  }

  let id = 'message_' + seed++
  let userOnClose = options.onClose
  
  // 组件关闭的时候执行close方法
  // 主要用来数组中移出实例,并重新计算偏移量
  options.onClose = function () {
    Message.close(id, userOnClose)
  }
  // 计算每个组件的偏移
  let verticalOffset = options.offset || 16
  instances.forEach(item => {
    verticalOffset += item.$el.offsetHeight + 16
  })
  options.verticalOffset = verticalOffset

  // 创建实例
  let instance = new MessageConstructor({
    data: options,
    el: document.createElement('div')
  })
  // 实例id赋值
  instance.id = id

  instances.push(instance)
  // 插入dom
  document.body.appendChild(instance.$el)
  return instance
}

Message.close = function (id, userOnClose) {
  let len = instances.length
  let index = -1
  index = instances.findIndex(item => {
    return item.id === id
  })
  if (index === -1) return
  const removedHeight = instances[index].$el.offsetHeight
  if (typeof userOnClose === 'function') {
    userOnClose(instances[index])
  }
  instances.splice(index, 1)
  
  // 重新计算偏移量
  if (len <= 1 || index > instances.length - 1) return
  for (let i = index; i < len - 1; i++) {
    let dom = instances[i].$el
    dom.style['top'] =
      parseInt(dom.style['top'], 10) - removedHeight - 16 + 'px'
  }
}

export default Message

注册组件

// 注册组件方法一:直接引入组件
// main.js
import Message from './components/message'
Vue.prototype.$message = Message

// 注册组件方法二:通过vue.use方式
// components/index.js
import Message from './components/message'
const install = function (Vue, ops = {}) {
  Vue.prototype.$message = Message
}
export default {
  install
}
// main.js
import ui from './src/components/index.js'
Vue.use(ui)