模拟系统的消息提示框而实现的一套模态对话框组件,用于消息提示、确认消息和提交内容。
概述:该组件的结构、原理与类似,所以这篇文章会减少组件开发的介绍,而增加一些Vue.extend
、Vue
生命周期相关源码的解读。
- Message-Box的基本功能实现;
Vue.extend
、$mount
原理以及相应的优化点。
1. 实例
代码
this.$confirm({ title: "自定义提示", content: `自定义HTML
`, onConfirm: () => this.$message({ content: "确定" }), onCancel: () => this.$message({ type: "warn", content: "取消" })});this.$alert({ title: "标题名称", content: "这是一段内容", onConfirm: () => this.$message({ content: "确定" })});复制代码
实例地址:
代码地址:
2. 原理
将Message-Box分为两部分:
- Message-Box组件,用于在页面中显示模态框,包含确认、取消、关闭三个操作;
- Vue插件的封装,利用
this.$alert({...})
或this.$confirm({...})
在页面中挂载 Message-Box 组件。
首先开发Message-Box组件,其基本template
如下
复制代码{ { title }}
基本结构非常简单,清晰的三段:
- 最上层的
visible
状态用于控制整个模态框的显示、消失,header部分,包含title
,以及关闭按键; - 中间content部分,包含传入的
content
,为了支持HTML,所以采用v-html
; - 最底层footer部分,包含两个按钮,如果当前模态框的类型是
alert
则只有confirm
,如果为confirm
,则需要添加cancel
。
所涉及的data
、methods
如下
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(); } }}复制代码
可以看到在该组件的mounted
、destroyed
两个生命周期中完成组件在页面中的挂载与注销
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
模态框组件,然后利用创建一个Message-Box组件的构造器。
当调用this.$alert
以及this.$confirm
时利用new Constructor
创建一个Message-Box组件,这时显式调用vm.$mount()
手动开启编译,此时会触发组件的mounted
生命周期
mounted() { document.body.appendChild(this.$el)}复制代码
完成在页面中的挂载。
最后一步,利用完成封装,在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 F
为 function 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 constructorcachedCtors[SuperId] = Sub复制代码
- 总结
深究了一下 Vue.extend
背后的原理,以及如何用它来是实现组件。
参考文章:
往期文章:
原创声明: 该文章为原创文章,转载请注明出处。