vue封装命令式组件

2,677 阅读1分钟

怎样封装一个 notify 命令式组件的

想成为一个vue框架中高级的coder,不会点组件封装是不行的。vue中注册组件的方式主要有三种,全局注册(Vue.component),局部注册(components:{componentsA}),还有就是命令式的插件形式,其调用形式一般是this.$Message({...}),
前面两种相对简单,今天我们就来封装一个命令式组件。

效果就是我点击中间的按钮,弹出右下角的提示,然后几秒后消息

效果图
notify效果

1.首先我们要有一个模板文件,这里我就命名为 notification.vue

<template>
  <transition name="fade" @after-leave="afterLeave" @after-enter="afterEnter">
    <div class="notification" :style="style" v-show="visible">
      <span class="content" mouseenter="clearTimer" mouseout="createTime">
        {{content}}
      </span>
      <a class="btn" @click="handleClick">
        {{btn}}
      </a>
    </div>
  </transition>
</template>

<script>
  export default {
    name: 'Notification',
    props: {
      content: {
        type: String,
        required: true
      },
      btn: {
        type: String,
        default: "关闭"
      }
    },
    mounted () {
    },
    computed: {
      style () {
        return {}
      }
    },
    data () {
      return {
        visible: true
      }
    },
    methods: {
      handleClick (e) {
        e.preventDefault();
        this.$emit('close')
      },
      afterLeave () {
        this.$emit('closed')
      },
      afterEnter () {
        this.height = this.$el.offsetHeight
      },
      clearTimer () {

      },
      createTime () { }
    },
  }
</script>

<style lang="scss" scoped>
  .notification {
    display: flex;
    background: #303030;
    color: rgba(255, 255, 255, 1);
    align-items: center;
    padding: 20px;
    position: fixed;
    min-width: 280px;
    box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2);
    flex-wrap: wrap;
    transition: all 0.2s;
    border-radius: 10px;
    .content {
      padding: 0;
    }
    .btn {
      color: #ff4081;
      padding-left: 24px;
      margin-left: auto;
      cursor: pointer;
    }
  }
</style>

2.继承上一步封装的模板文件,添加我们定制的数据,命名为 func-notification.js

import Notification from './notification.vue'

export default {
  extends: Notification,
  computed: {
    style() {
      return {
        position: 'fixed',
        right: '20px',
        bottom: `${this.verticleOffset}px`
      }
    }
  },
  mounted() {
    this.createTimer()
  },
  methods: {
    createTimer() {
      if (this.autoClose) {
        this.timer = setTimeout(() => {
          this.visible = false
        }, this.autoClose)
      }
    },
    clearTimer() {
      if (this.timer) {
        clearTimeout(this.timer)
      }
    }
  },
  destroyed() {
    this.clearTimer()
  },
  data() {
    return {
      verticleOffset: 0,
      autoClose: 3000,
      height: 0,
      visible: false
    }
  }
}

3.新建 functions.js 文件,引入上面两个文件然后再扩展其功能

import Vue from 'vue'
import Comp from './func-notification'

const NotificationConstructor = Vue.extend(Comp)

const instances = []

let seed = 1

const removeInstance = instance => {
  if (!instance) return
  const len = instances.length
  const index = instances.findIndex(inst => instance.id == inst.id)
  instances.splice(index, 1)
  if (len < 1) return
  const removeHeight = instance.height
  for (var i = index; i < len - 1; i++) {
    instances[i].verticleOffset =
      parseInt(instances[i].verticleOffset) - removeHeight - 16
  }
}

const notify = options => {
  if (Vue.prototype.$isServer) {
    return
  }
  const { autoClose, ...rest } = options
  const instance = new NotificationConstructor({
    propsData: {
      ...rest
    },
    data() {
      return {
        autoClose: autoClose === undefined ? 3000 : autoClose
      }
    }
  })
  const id = `notification_${seed++}`
  instance.id = id

  instance.$mount()
  document.body.appendChild(instance.$el)
  instance.visible = true

  let verticleOffset = 0

  instances.forEach(item => {
    verticleOffset += item.$el.offsetHeight + 16
  })
  verticleOffset += 16
  instance.verticleOffset = verticleOffset
  /*  */
  console.log(instance)
  instances.push(instance)
  instance.$on('closed', () => {
    removeInstance(instance)
    document.body.removeChild(instance.$el)
    instance.$destroy()
  })
  instance.$on('close', () => {
    instance.visible = false
  })
}
export default notify

4.新建 index.js 作为整个组件的入口文件

import Notification from './notification.vue'
import notify from './functions'

export default Vue => {
  Vue.component(Notification.name, Notification)
  Vue.prototype.$notify = notify
}

5.在 main.js 里面导入组件,并进行注册,这样就可以在全局进行命令式调用了

...
import Notification from './component/notification'
...
Vue.use(Notification)

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')