从零实现Vue的组件库(七)- Message-Box 实现

模拟系统的消息提示框而实现的一套模态对话框组件,用于消息提示、确认消息和提交内容。

概述:该组件的结构、原理与Toast 组件类似,所以这篇文章会减少组件开发的介绍,而增加一些Vue.extendVue生命周期相关源码的解读。

主要有以下几点:
  • Message-Box的基本功能实现;
  • Vue.extend$mount原理以及相应的优化点。

1. 实例

代码

<!-- 基础用法 -->
this.$confirm({
    title: "自定义提示",
    content: `<h1 style="color: red;">自定义HTML</h1>`,
    onConfirm: () => this.$message({
        content: "确定"
    }),
    onCancel: () => this.$message({
        type: "warn",
        content: "取消"
    })
});
this.$alert({
    title: "标题名称",
    content: "这是一段内容",
    onConfirm: () =>
    this.$message({
        content: "确定"
    })
});
复制代码

实例地址:Hover-Tip 实例

代码地址:Github UI-Library

2. 原理

将Message-Box分为两部分:

  • Message-Box组件,用于在页面中显示模态框,包含确认、取消、关闭三个操作;
  • Vue插件的封装,利用this.$alert({...})this.$confirm({...})在页面中挂载 Message-Box 组件。

首先开发Message-Box组件,其基本template如下

<div class="mock" v-if="visible">
    <div :class="['message-box']">
        <!-- header -->
        <h5 class="message-box-header c-size-l">
            <span>{{ title }}</span>
            <fat-icon 
                v-if="showClose" 
                name="close" 
                class="close-btn" 
                @click.stop="close"
            />
        </h5>
        <!-- content -->
        <div 
            class="message-box-content c-size-m" 
            v-html="content"
        >
        </div>
        <!-- footer -->
        <div class="message-box-footer">
            <fat-button
                size="mini"
                v-if="cancelButtonText && type !== 'alert'"
                @click.stop="handleClick('cancel')"
            >{{ cancelButtonText }}</fat-button>
            <fat-button
                size="mini"
                type="success"
                v-if="confirmButtonText"
                @click.stop="handleClick('confirm')"
            >{{ confirmButtonText }}</fat-button>
        </div>
    </div>
</div>
复制代码

基本结构非常简单,清晰的三段:

  • 最上层的visible状态用于控制整个模态框的显示、消失,header部分,包含title,以及关闭按键;
  • 中间content部分,包含传入的content,为了支持HTML,所以采用v-html
  • 最底层footer部分,包含两个按钮,如果当前模态框的类型是alert则只有confirm,如果为confirm,则需要添加cancel

所涉及的datamethods如下

export default {
    data() {
        return {
            // 控制模态框的显示
            visible: true
        }
    },
    watch: {
        visible(newValue) {
            if (!newValue) {
                // 过渡结束后注销组件
                this.$el.addEventListener('transitionend', this.destroyElement)
            }
        }
    },
    mounted() {
        document.body.appendChild(this.$el)
    },
    destroyed() {
        this.$el.parentNode.removeChild(this.$el)
    },
    methods: {
        destroyElement() {
            this.$destroy()
        },
        close() {
            // 关闭模态框
            this.visible = false
        },
        handleClick(type) {
            // 处理对应的点击事件
            this.$emit(type);
            this.close();
        }
    }
}
复制代码

可以看到在该组件的mounteddestroyed两个生命周期中完成组件在页面中的挂载与注销

mounted() {
    document.body.appendChild(this.$el)
},
destroyed() {
    this.$el.parentNode.removeChild(this.$el)
}
复制代码

其中注销的顺序是:

  • 首先使组件的visible状态为false,使它在页面中消失,然后触发模态框transition的过度动画;
  • 之后监听addEventListener('transitionend', this.destroyElement),如果过渡结束,就触发对应的destroyElement触发组件的生命周期destroyed,在组件中注销this.$el.parentNode.removeChild(this.$el)

相对于注销,挂载则要复杂的一些,由于这部分涉及到了封装,所以一起梳理。

// 引入上述Message-Box组件
import messageBox from './messagebox.vue'
// 生成Message-Box对应的构造器
const Constructor = Vue.extend(messageBox)

function generateInstance(options, type = 'alert') {
    let instance = new Constructor({
        propsData: Object.assign(options, {
            type
        }),
    }).$mount(document.createElement('div'))
    ...
    return instance
}
复制代码

首先import模态框组件,然后利用Vue.extend创建一个Message-Box组件的构造器。

当调用this.$alert以及this.$confirm时利用new Constructor创建一个Message-Box组件,这时显式调用vm.$mount()手动开启编译,此时会触发组件的mounted生命周期

mounted() {
    document.body.appendChild(this.$el)
}
复制代码

完成在页面中的挂载。

最后一步,利用Vue.use完成封装,在Vue.prototype上添加$alert以及$confirm方法。

export default {
    install(Vue) {
        Vue.prototype.$alert = (options = {}) => generateInstance(options)
        Vue.prototype.$confirm = (options = {}) => generateInstance(options, 'confirm')
    }
}
复制代码
3. 源码

阐述下 Vue.extend 的源码,在 src/core/global-api/extend.js 中,比较关键的点在于:

  • 如何通过已有组件 object 生成对应构造器 constructor
  • 对于同一个组件的 constructor ,是否存在什么优化。
Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
        return cachedCtors[SuperId]
    }
    ...    
    const Sub = function VueComponent (options) {
        this._init(options)
    }
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    ...
    // cache constructor
    cachedCtors[SuperId] = Sub
    return Sub
}
复制代码

从源码中可以看出,Vue.extend 是一个变式的原型式继承

function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}
复制代码

临时的构造函数 function Ffunction VueComponent,函数中 this._init 指向的是初始化Vue时候的 _init function

在将 F.prototype 指向 Object.create(Super.prototype) ,这样可以继承 Vue 本身原型上的一些方法,最后 return constructor

之后 Vue 还做了缓存处理,所以多次利用 Vue.extend 创建Message-Box、Toast、Message时,并不会影响效率/

if (cachedCtors[SuperId]) {
    return cachedCtors[SuperId]
}

// cache constructor
cachedCtors[SuperId] = Sub
复制代码
  1. 总结

深究了一下 Vue.extend 背后的原理,以及如何用它来是实现组件。

参考文章:

往期文章:

原创声明: 该文章为原创文章,转载请注明出处。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值