el-message 源码解析
学习element-ui Message组件的源码
基本结构
message组件的基本结构是一个div,使用vue中的transition组件进行过度动画。
<transition name="el-message-fade" @after-leave="handleAfterLeave">
<div
:class="[
'el-message',
type && !iconClass ? `el-message--${ type }` : '',
center ? 'is-center' : '',
showClose ? 'is-closable' : '',
customClass
]"
:style="positionStyle"
v-show="visible"
@mouseenter="clearTimer"
@mouseleave="startTimer"
role="alert">
<i :class="iconClass" v-if="iconClass"></i>
<i :class="typeClass" v-else></i>
<slot>
<p v-if="!dangerouslyUseHTMLString" class="el-message__content">{{ message }}</p>
<p v-else v-html="message" class="el-message__content"></p>
</slot>
<i v-if="showClose" class="el-message__closeBtn el-icon-close" @click="close"></i>
</div>
</transition>
</template>
过度动画的css
.el-message-fade-enter,
.el-message-fade-leave-active {
opacity: 0;
transform: translate(-50%, -100%);
}
全局方法
message组件可以通过函数的形式调用
const Message = function(options) {
if (Vue.prototype.$isServer) return;
options = options || {};
// 判断传给message组件的options是否为string
// 如果是的话直接给组件中的data message 赋值
if (typeof options === 'string') {
options = {
message: options
};
}
// 用户自定义的close方法
let userOnClose = options.onClose;
// message id
let id = 'message_' + seed++;
// 封装close方法,调用用户传进来的close
options.onClose = function() {
Message.close(id, userOnClose);
};
// message instance
instance = new MessageConstructor({
data: options
});
instance.id = id;
// 判断message是否为VNode
// 是VNode 会将组件中的slot.default赋值为传进来的VNode,并且将message设置为null
// 此时组件不再使用定义的slot后备内容,而是直接渲染用户的VNode
if (isVNode(instance.message)) {
instance.$slots.default = [instance.message];
instance.message = null;
}
// 挂载实例
instance.$mount();
// 将实例绑定真实dom
document.body.appendChild(instance.$el);
// 垂直偏移距离
let verticalOffset = options.offset || 20;
// 如果有其他的实例,会将新显示message组件继续向下偏移
instances.forEach(item => {
verticalOffset += item.$el.offsetHeight + 16;
});
instance.verticalOffset = verticalOffset;
// 显示message组件
instance.visible = true;
instance.$el.style.zIndex = PopupManager.nextZIndex();
instances.push(instance);
return instance;
};
基本用法
可以接受一个message props,或者是一个vNode参数
this.$message('这是一条消息提示');
const h = this.$createElement;
this.$message({
message: h('p', null, [
h('span', null, '内容可以是 '),
h('i', { style: 'color: teal' }, 'VNode')
])
});
不同的状态
不同的状态会显示对应的class
type && !iconClass ? `el-message--${ type }` : '',
如果用户不传递iconClass,就会使用默认的typeClass
<i :class="iconClass" v-if="iconClass"></i>
<i :class="typeClass" v-else></i>
typeClass() {
return this.type && !this.iconClass
? `el-message__icon el-icon-${ typeMap[this.type] }`
: '';
},
可关闭
设置showClose属性,会在组件上显示close,点击会设置visible属性为false,并且调用了用户的close方法
// methods
close() {
this.closed = true;
if (typeof this.onClose === 'function') {
this.onClose(this);
}
},
watch: {
closed(newVal) {
if (newVal) {
this.visible = false;
}
}
},
可以通过设置duration来控制组件的关闭时间,如果设置为0则组件不会自动关闭,默认为 3000 毫秒
源码中是通过一个定时器来控制的,如果duration为0,则不会开启定时器
mounted() {
this.startTimer();
},
startTimer() {
if (this.duration > 0) {
this.timer = setTimeout(() => {
if (!this.closed) {
this.close();
}
}, this.duration);
}
},
文字居中
设置center属性可以使文字居中,使用的是justify-content: center 属性
@include when(center) {
justify-content: center;
}
使用HTML片段
将dangerouslyUseHTMLString属性设置为 true,message 就会被当作 HTML 片段处理。
<slot>
<p v-if="!dangerouslyUseHTMLString" class="el-message__content">{{ message }}</p>
<p v-else v-html="message" class="el-message__content"></p>
</slot>
全局其他方法
实际上就是将Message instance注册了一个相对应的函数,并且给Message组件传递了type属性
['success', 'warning', 'info', 'error'].forEach(type => {
Message[type] = (options) => {
if (isObject(options) && !isVNode(options)) {
return Message({
...options,
type
});
}
return Message({
type,
message: options
});
};
});
关闭方法
Message.close = function(id, userOnClose) {
let len = instances.length;
let index = -1;
let removedHeight;
// 遍历所有实例
for (let i = 0; i < len; i++) {
if (id === instances[i].id) {
removedHeight = instances[i].$el.offsetHeight;
index = i;
if (typeof userOnClose === 'function') {
// 调用用户传递的close方法
userOnClose(instances[i]);
}
// 删除对应的instance
instances.splice(i, 1);
break;
}
}
// 只有一个实例时 直接返回
if (len <= 1 || index === -1 || index > instances.length - 1) return;
// 遍历其他实例,将其他message组件的top属性,重新设置
for (let i = index; i < len - 1 ; i++) {
let dom = instances[i].$el;
dom.style['top'] =
parseInt(dom.style['top'], 10) - removedHeight - 16 + 'px';
}
};
// 关闭所有实例
Message.closeAll = function() {
for (let i = instances.length - 1; i >= 0; i--) {
instances[i].close();
}
};
全局唯一的message
有时项目中可能多次显示组件时,需要关闭上一个message,防止出现多个,可以使用以下代码实现
let messageStatus = null;
export class ShowMessage {
success(options) {
this.message("success", options);
}
warning(options) {
this.message("warning", options);
}
info(options) {
this.message("info", options);
}
error(options) {
this.message("error", options);
}
message(type, options) {
if (messageStatus) {
messageStatus.close();
}
messageStatus = Message[type](options);
}
}
import ShowMessage from '@/config/message';
Vue.prototype.$message = new ShowMessage();