因最近业务需求需要实现类似于Element中的MessageBox组件的效果,所以尝试封装了一个类似的小组件,本文不介绍封装,因为受到MessageBox的启发,所以通过源码注释的方式详细剖析一下Element的MessageBox实现思想。
基础知识
Vue.extend(options)
- 参数:{Object} options
- 用法:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。需要注意的是:data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数
<div id="mount-point"></div>
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
结果:
<p>Walter White aka Heisenberg</p>
为什么要介绍Vue.extend(options),因为Element中MessageBox的使用方式为函数式调用 (this.$confirm())的形式,以这种形式调用组件就不能按照常规的组件引入注册的形式去调用,可以通过Vue.extend(options)创建一个vue子类再通过函数暴露出去的方式实现函数式调用。
Element MessageBox 源码
// main.js 仅介绍主要部分代码
...
import Vue from 'vue';
// 引入模板文件
import msgboxVue from './main.vue';
// element合并对象的工具函数,代码比较简单可以自行查阅
import merge from 'element-ui/src/utils/merge';
import { isVNode } from 'element-ui/src/utils/vdom';
// 创建一个新的vue构造器,构造器可以手动挂载到一个新的Dom上
const MessageBoxConstructor = Vue.extend(msgboxVue);
let currentMsg, instance;
let msgQueue = [];
// 创建一个新的vue子实例
const initInstance = () => {
instance = new MessageBoxConstructor({
el: document.createElement('div')
});
// 给实例添加callback对象,后面会分析到
instance.callback = defaultCallback;
};
// defaultCallback处理了两种形式的回调方式
// 1.可以手动传入一个callback函数
// 2.使用默认的promise方式
const defaultCallback = action => {
if (currentMsg) {
let callback = currentMsg.callback;
// 处理传入回调函数情况
if (typeof callback === 'function') {
// showInput区分是否为输入框MessageBox
if (instance.showInput) {
callback(instance.inputValue, action);
} else {
callback(action, instance);
}
}
// 处理promise情况
if (currentMsg.resolve) {
if (action === 'confirm') {
if (instance.showInput) {
currentMsg.resolve({ value: instance.inputValue, action });
} else {
currentMsg.resolve(action);
}
} else if (currentMsg.reject && (action === 'cancel' || action === 'close')) {
currentMsg.reject(action);
}
}
}
};
const showNextMsg = () => {
if (!instance) {
initInstance();
}
instance.action = '';
if (!instance.visible || instance.closeTimer) {
if (msgQueue.length > 0) {
// 顺序执行msgQueue中的currentMsg
currentMsg = msgQueue.shift();
// currentMsg内容如下:
// {
// options: merge({}, defaults, MessageBox.defaults, options),
// callback: callback,
// resolve: resolve,
// reject: reject
// }
let options = currentMsg.options;
// 将参数挂载到新创建的实例data上
for (let prop in options) {
if (options.hasOwnProperty(prop)) {
// 实例参数修改
instance[prop] = options[prop];
}
}
// 如果options没传入callback将默认的callback赋值给实例的callback
if (options.callback === undefined) {
// 当options里面有callback传入,正常输出。
// 当options里面没有callback,instance.callback使用defaultCallback
instance.callback = defaultCallback;
}
// 再次封装callback
let oldCb = instance.callback;
instance.callback = (action, instance) => {
oldCb(action, instance);
showNextMsg();
};
// 判断message是否传入的是Html片段,如果是Html片段添加到slot
if (isVNode(instance.message)) {
instance.$slots.default = [instance.message];
instance.message = null;
} else {
delete instance.$slots.default;
}
// 将某些特定的参数设定初始值
['modal', 'showClose', 'closeOnClickModal', 'closeOnPressEscape', 'closeOnHashChange'].forEach(prop => {
if (instance[prop] === undefined) {
instance[prop] = true;
}
});
// 注意message是挂载到body上
document.body.appendChild(instance.$el);
// 控制弹窗出现
Vue.nextTick(() => {
instance.visible = true;
});
}
}
};
const MessageBox = function(options, callback) {
if (Vue.prototype.$isServer) return;
if (typeof options === 'string' || isVNode(options)) {
// 当options参数为字符串 this.$msgbox('xxx')情况下默认设置message字段
options = {
message: options
};
// 若有两个及以上参数判断第二个参数是否为字符串赋值给title
if (typeof arguments[1] === 'string') {
options.title = arguments[1];
}
} else if (options.callback && !callback) {
// 参数为对象且对象有callback字段时 将callback赋值给callback
callback = options.callback;
}
// 兼容不支持Promise情况
if (typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => { // eslint-disable-line
msgQueue.push({
options: merge({}, defaults, MessageBox.defaults, options),
callback: callback,
resolve: resolve,
reject: reject
});
showNextMsg();
});
} else {
msgQueue.push({
options: merge({}, defaults, MessageBox.defaults, options),
callback: callback
});
showNextMsg();
}
};
// 使用方式1:
// this.$msgbox({title:'测试',message:'测试',callback:(action,instance)=>{
// console.log(action) //confirm
// console.log(instance) //vue实例
// }})
// 使用方式2:
// this.$msgbox({title:'测试',message:'测试'},(action,instance)=>{
// console.log(action) //confirm
// console.log(instance) //undefined
// })
...
// 暴露MessageBox方法
export default MessageBox;
export { MessageBox };
可以看出Message组件主要的两个方法一个是MessageBox,一个是showNextMsg,这两个方法的主要功能一个是添加新的message对象一个是设置实例参数,除了callback的理解有些复杂外其他的代码理解应该不难。至于main.vue文件就是普通的vue文件不再赘述。