天天看点

用vue写轮子的一些心得(二)——toast组件

注册一个插件

我们知道在vue中,如果需要在全局增加一个自己使用的对象,在vue的原型中:vue.prototype.xxx 后面接自己的对象通常是没有问题的,但是在项目中vue的原型是不能乱去修改的,因为一旦原型中有两个同名的对象,极有可能会出现灾难性的后果。因此我们需要使用vue中提到的一种方法 Vue.use( plugin ),注册一个插件,集中性地管理这些将被添加到Vue.prototyp中的插件。

使用插件:

通过全局方法 

Vue.use()

 使用插件。它需要在你调用 

new Vue()

 启动应用之前完成:

// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)

new Vue({
  // ...组件选项
})
           

也可以传入一个可选的选项对象:

Vue.use(MyPlugin, { someOption: true })
           

Vue.use 会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。

然后在插件中写入逻辑:

Vue.js 的插件应该暴露一个 

install

 方法。这个方法的第一个参数是 

Vue

 构造器,第二个参数是一个可选的选项对象。

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或属性
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })

  // 3. 注入组件选项
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}
           

实现toast组件逻辑

从应用页面组件触发toast事件——>plugin插件中收到传递的参数处理后再传给toast组件——>toast组件收到plugin传递的参数开始执行相关的方法。

应用页面组件触发toast事件:

created() {
    this.$toast('这是内容',{
        closeButton:{
            text: '知道了',
            callback() {  // 可以接受组件回传来的某些重要的功能
                console.log('用户知道了');
            }
        },
    })
},
           

plugin插件中收到传递的参数处理后再传给toast组件:

import tToast from './toast'

let currentToast = null

export default {
    install(Vue, options){
        Vue.prototype.$toast = function (message, toastOptions) {
            if (currentToast) {
                currentToast.close()
            }
            currentToast = createToast({
                Vue,
                message,
                propsData: toastOptions,
                onClose: () => {
                    currentToast = null
                }
            })
        }
    }
}

function createToast({Vue, message,propsData, onClose}) {
    let Construtor = Vue.extend(tToast)
    let toast = new Construtor({propsData})
    toast.$slots.default = [message]
    toast.$mount()
    toast.$on('close', onClose)
    document.body.appendChild(toast.$el)
    return toast
}
           

toast组件收到plugin传递的参数开始执行相关的方法:

props接受4个参数,1、是否自动关闭弹框。2、关闭按钮文案及关闭回调。3、是否可开启传html。4、toast弹出位置选择。

export default {
    name: "tToast",
    props: {
        autoClose: { // 是否自动关闭弹框
            type: [Boolean, Number],
            default: 2,
            validator(value) {
                return value === false || typeof value === 'number'
            }
        },
        closeButton: {
            type: Object,
            default() {
                return {
                    text: '关闭',
                    callback: undefined
                }
            }
        },
        enableHtml: {  // 默认不开启传html
            type: Boolean,
            default: false
        },
        position: {
            type: String,
            default: 'top',
            validator(value) {
                return ['top', 'middle', 'bottom'].indexOf(value) >= 0
            }
        }
    },

    mounted() {
        this.updateStyles()
        this.execAutoClose()
    },

    methods: {
        updateStyles() {
            this.$nextTick(() => {
                this.$refs.line.style.height = `${this.$refs.wrapper.getBoundingClientRect().height}px`
            })
        },
        execAutoClose() {
            if (this.autoClose) {
                setTimeout(() => {
                    this.close()
                }, this.autoClose * 1000)
            }
        },
        close() {
            this.$el.remove() //删掉当前组件DOM节点
            this.$emit('close')
            this.$destroy()
        },
        onClickClose() {
            this.close()
            if (this.closeButton && typeof this.closeButton.callback === 'function') {
                this.closeButton.callback(this) // this === toast实例
            }
        },
    }
}
           

需要注意的点

如何动态获取DOM在页面中的坐标位置?

通过Element.getBoundingClientRect()方法可以返回元素的大小及其相对于视口的位置

用vue写轮子的一些心得(二)——toast组件

这样就能拿到元素相对与页面的坐标值——left、top、right和bottom,单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。这样我们就能实现toast组件在页面中的定位显示,可以相对于body也可以相对于任何一个元素的位置,具有很大的自由度。

获取dom元素位置的问题

当我们在mounted生命周期钩子中调用Element.getBoundingClientRect()方法,会拿不到元素的坐标值,因为是先mount挂载,再在plugin插件中appendChild动态添加到body元素中的,因此需要在下一次DOM队列执行的时候才能拿到,这里我们就需要使用vue中的$nextTick() (在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM)。

this.$nextTick(() => {
    this.$refs.line.style.height = `${this.$refs.wrapper.getBoundingClientRect().height}px`
})