寄语:参加若川组织的【源码共读】第28期——看Element-UI的message组件源码,期望收获更好的自己~~ 活动链接:www.yuque.com/ruochuan12/…
话不多说,正文开始!
首先说明一下:看的是element-ui给vue2使用的message组件。
一、使用用例
- element-ui提供的使用用例之一
默认的 Message 是不可以被人工关闭的,如果需要可手动关闭的 Message,可以使用showClose字段。此外,和 Notification 一样,Message 拥有可控的duration,设置0为不会被自动关闭,默认为 3000 毫秒。
<template>
<!--下面2个按钮对应上图1:-->
<el-button :plain="true" @click="open">打开消息提示</el-button>
<el-button :plain="true" @click="openVn">VNode</el-button>
<!--下面4个按钮对应上图2:-->
<el-button :plain="true" @click="open1">消息</el-button>
<el-button :plain="true" @click="open2">成功</el-button>
<el-button :plain="true" @click="open3">警告</el-button>
<el-button :plain="true" @click="open4">错误</el-button>
<!--另一种写法:this.$message.error("这是一条错误信息!")-->
<el-button :plain="true" @click="open5">错误</el-button>
</template>
<script>
export default {
methods: {
// 传参:string类型
open() {
this.$message('这是一条消息提示');
},
// 传参:VNode类型
openVn() {
const h = this.$createElement;
this.$message({
message: h('p', null, [
h('span', null, '内容可以是 '),
h('i', { style: 'color: teal' }, 'VNode')
])
});
},
// 下面4个传参:都是对象
open1() {
this.$message({
showClose: true,
message: '这是一条消息提示'
});
},
open2() {
this.$message({
showClose: true,
message: '恭喜你,这是一条成功消息',
type: 'success'
});
},
open3() {
this.$message({
showClose: true,
message: '警告哦,这是一条警告消息',
type: 'warning'
});
},
open4() {
this.$message({
showClose: true,
message: '错了哦,这是一条错误消息',
type: 'error'
});
},
// -------------------------------------------
open5(){
this.$message.error('错了哦,这是一条错误消息');
}
}
}
</script>
- 对上述用例的分析:
a. this.message是Message组件的构造函数,并且element-ui把$message挂在了Vue的原型对象上,所以才能用this来调用。
b. 构造函数$message在new的过程中,传的参可以是String类型,也可以是VNode类型,还可以是对象类型.
c. Message具体的构造函数看源码packages/message/src/main.js里的:
const Message = function(options){ }
d. Message 里面有new实例化的关键一步:
instance = new MessageConstructor({
data: options
});
\
二、源码
在线看源码:github1s.com/ElemeFE/ele…
源码如下:
- 结构:packages/message
\
- 具体源码:
(1)packages/message/src/main.vue:
<template>
<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>
<script type="text/babel">
const typeMap = {
success: 'success',
info: 'info',
warning: 'warning',
error: 'error'
};
export default {
data() {
return {
visible: false,
message: '',
duration: 3000,
type: 'info',
iconClass: '',
customClass: '',
onClose: null,
showClose: false,
closed: false,
verticalOffset: 20,
timer: null,
dangerouslyUseHTMLString: false,
center: false
};
},
computed: {
typeClass() {
return this.type && !this.iconClass
? `el-message__icon el-icon-${ typeMap[this.type] }`
: '';
},
positionStyle() {
return {
'top': `${ this.verticalOffset }px`
};
}
},
watch: {
closed(newVal) {
if (newVal) {
this.visible = false;
}
}
},
methods: {
handleAfterLeave() {
this.$destroy(true);
this.$el.parentNode.removeChild(this.$el);
},
close() {
this.closed = true;
if (typeof this.onClose === 'function') {
this.onClose(this);
}
},
clearTimer() {
clearTimeout(this.timer);
},
startTimer() {
if (this.duration > 0) {
this.timer = setTimeout(() => {
if (!this.closed) {
this.close();
}
}, this.duration);
}
},
keydown(e) {
if (e.keyCode === 27) { // esc关闭消息
if (!this.closed) {
this.close();
}
}
}
},
mounted() {
this.startTimer();
document.addEventListener('keydown', this.keydown);
},
beforeDestroy() {
document.removeEventListener('keydown', this.keydown);
}
};
</script>
以上代码涉及到的点:
a. transition: Vue使用transition来实现过渡动画
name="el-message-fade",也就是在设置动画的类名前加el-message-fade。
(截图为packages/theme-chalk/src/message.scss里面的部分代码)
b. role属性:是一种语义化的表现,除了上述源码中的role="alert"之外,还可以是role="form",role="button"等。
在HTML5之前,只能通过role属性来定义landmark
c. 消息弹框的停留时间:
点击后默认显示3秒再自动关闭——在mouted阶段调用this.startTimer(),里面开了定时器去定时关闭;
按esc键可以关闭弹框——在mouted阶段绑定keydown事件:document.addEventListener('keydown', this.keydown);
如果鼠标移入到消息弹框里,弹框显示时长延长3秒:元素上绑定了mouseenter事件;
如果鼠标移出消息弹框,3秒后弹框关闭:在元素上绑定了mouseleave事件。
d. 关闭的逻辑:
-
- showClose:用这个变量来控制有没有关闭按钮。
具体从哪里传的,又是怎么接收的,看下面:
showClose作为参数对象的属性传进来,在Message内部是用vue的data的showClose来接收。
-
- close函数与closed属性值:关闭按钮上绑定了点击事件,一点击就会触发close函数,在close函数里面把属性closed的值设置为true,然后watch监听了closed的更新,一被更改,就把visible的值设置为false,隐藏掉消息弹框;close函数里还调用了onClose函数——这里处理关闭后执行的操作。
-
- onClose:element-ui的关闭事件,即关闭后执行的操作放这里。onClose的主要逻辑看packages/message/src/main.js。
所以main.vue里的this.onClose(this),实际调用的是main.js里的userOnClose(instances[i]):
(2)packages/message/src/main.js:
import Vue from 'vue';
import Main from './main.vue';
import { PopupManager } from 'element-ui/src/utils/popup';
import { isVNode } from 'element-ui/src/utils/vdom';
import { isObject } from 'element-ui/src/utils/types';
// 消息构造器
let MessageConstructor = Vue.extend(Main);
let instance;
let instances = [];
let seed = 1;
const Message = function(options) {
if (Vue.prototype.$isServer) return;
options = options || {};
if (typeof options === 'string') {
options = {
message: options
};
}
let userOnClose = options.onClose;
let id = 'message_' + seed++;
options.onClose = function() {
Message.close(id, userOnClose);
};
instance = new MessageConstructor({
data: options
});
instance.id = id;
if (isVNode(instance.message)) {
instance.$slots.default = [instance.message];
instance.message = null;
}
instance.$mount();
document.body.appendChild(instance.$el);
let verticalOffset = options.offset || 20;
instances.forEach(item => {
// 每个Message实例离页面顶部的垂直距离在上一个message弹框的基础上加上16px.
verticalOffset += item.$el.offsetHeight + 16;
});
instance.verticalOffset = verticalOffset;
instance.visible = true;
instance.$el.style.zIndex = PopupManager.nextZIndex();
instances.push(instance);
return instance;
};
['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; // message弹框的个数
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') {
userOnClose(instances[i]);
}
instances.splice(i, 1);
break;
}
}
if (len <= 1 || index === -1 || index > instances.length - 1) return;
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();
}
};
export default Message;
a. Message实例化:定义Message函数
-
-
- 构造一个消息构造器MessageConstructor:let MessageConstructor = Vue.extend(Main);
- 对传来的参数options做处理:
-
this.$message("这是一条消息!");
传来的参数用main.js的options来接收,当参数是String类型的时候,把options变成对象。也就是:
"这是一条消息!" ==> {message: "这是一条消息!"}
-
-
- new一个MessageConstructor: instance
-
instance = new MessageConstructor({
data: options
});
-
-
- instance.$mount();
- Vue.extend + vm.$mount组合使用:Vue中 Vue.extend() 详解及使用
- 初始化一些属性值:比如处理传消息message属性传的是VNode类型时要做一些处理(插槽、把message置为null),还要设置instance的id、verticalOffset、visible等属性值。
-
b. this.$message.success( / warning / info / error):
this.$message.success("这是一条成功消息!")能起作用,主要是Message里面定义了success、warning、info、error四个方法,方法里面对参数做了是字符串还是对象、VNode的判断,再调用了Message函数——创建了实例。
c. 在Message里面定义了close方法和closeAll方法。 其中close方法里面,既处理了关闭消息的弹框的逻辑,又处理了用户使用在关闭弹框后的一些操作——即:关闭后的回调函数.
使用举例:
/*
message:提示信息
offset: 距离窗口顶部偏移量
duration: 显示时间,设置0时,不会自动关闭
onClose: 回调函数
*/
this.$message.success({
message: "审核成功",
offset:220,
duration: 2000,
onClose: () => {
this.$router.go(-1) // 消息弹框关闭后,页面回到之前一个。
}
})
\
(3)css:packages/theme-chalk/src/message.scss:
@import "mixins/mixins";
@import "common/var";
@include b(message) {
min-width: $--message-min-width;
box-sizing: border-box;
border-radius: $--border-radius-base;
border-width: $--border-width-base;
border-style: $--border-style-base;
border-color: $--border-color-lighter;
position: fixed;
left: 50%;
top: 20px;
transform: translateX(-50%);
background-color: $--message-background-color;
transition: opacity 0.3s, transform .4s, top 0.4s;
overflow: hidden;
padding: $--message-padding;
display: flex;
align-items: center;
@include when(center) {
justify-content: center;
}
@include when(closable) {
.el-message__content {
padding-right: 16px;
}
}
p {
margin: 0;
}
@include m(info) {
.el-message__content {
color: $--message-info-font-color;
}
}
@include m(success) {
background-color: $--color-success-lighter;
border-color: $--color-success-light;
.el-message__content {
color: $--message-success-font-color;
}
}
@include m(warning) {
background-color: $--color-warning-lighter;
border-color: $--color-warning-light;
.el-message__content {
color: $--message-warning-font-color;
}
}
@include m(error) {
background-color: $--color-danger-lighter;
border-color: $--color-danger-light;
.el-message__content {
color: $--message-danger-font-color;
}
}
@include e(icon) {
margin-right: 10px;
}
@include e(content) {
padding: 0;
font-size: 14px;
line-height: 1;
&:focus {
outline-width: 0;
}
}
@include e(closeBtn) {
position: absolute;
top: 50%;
right: 15px;
transform: translateY(-50%);
cursor: pointer;
color: $--message-close-icon-color;
font-size: $--message-close-size;
&:focus {
outline-width: 0;
}
&:hover {
color: $--message-close-hover-color;
}
}
& .el-icon-success {
color: $--message-success-font-color;
}
& .el-icon-error {
color: $--message-danger-font-color;
}
& .el-icon-info {
color: $--message-info-font-color;
}
& .el-icon-warning {
color: $--message-warning-font-color;
}
}
.el-message-fade-enter,
.el-message-fade-leave-active {
opacity: 0;
transform: translate(-50%, -100%);
}
消息弹框的样式:用了mixins和scss。
先来学习下mixins,看下面的资料:
a. scss + mixins: scss混合”mixins“使用
b. Element scss mixins 源码学习:
element-ui源码解读-基于scss的bem方法的实现
src/mixins/emitter.js中有两个方法:dispatch和broadcast
再来学习scss:
scss是什么:Sass是成熟、稳定、强大的CSS预处理器,而SCSS是Sass3版本当中引入的新语法特性,完全兼容CSS3的同时继承了Sass强大的动态功能。
官方教程太多了,一时半会看不完,那就看下面的快速上手吧!
听说你还不会SCSS?带你掌握scss所有知识点(1):Sass的介绍、安装和文件编译
听说你还不会SCSS?带你掌握scss所有知识点(2):掌握Scss基础语法和使用、局部文件导入
\
(4)packages/message/index.js:
import Message from './src/main.js';
export default Message;
用了ES6的模块化导入导出。
(5)src/index.js:
把封装好的message组件挂在Vue的原型上,以后就可以用this进行全局调用
源码如下,我删掉了element-ui的组件,留下了与Message相似的MessageBox和Notification:
/* Automatically generated by './build/bin/build-entry.js' */
import Message from '../packages/message/index.js';
import MessageBox from '../packages/message-box/index.js';
import Notification from '../packages/notification/index.js';
import locale from 'element-ui/src/locale';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
const components = [
];
const install = function(Vue, opts = {}) {
locale.use(opts.locale);
locale.i18n(opts.i18n);
components.forEach(component => {
Vue.component(component.name, component);
});
Vue.use(InfiniteScroll);
Vue.use(Loading.directive);
Vue.prototype.$ELEMENT = {
size: opts.size || '',
zIndex: opts.zIndex || 2000
};
Vue.prototype.$loading = Loading.service;
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$prompt = MessageBox.prompt;
Vue.prototype.$notify = Notification;
Vue.prototype.$message = Message;
};
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default {
version: '2.15.8',
locale: locale.use,
i18n: locale.i18n,
install,
CollapseTransition,
Loading,
Message,
MessageBox,
Notification
};
把Message挂在Vue的原型对象prototype上。
以后就可以在项目全局使用this.$message( ) 触发消息弹框了,具体使用用例看最上面举的element-ui官方的例子,参数可以传字符串,可以传VNode,也可以传对象。
三、总结:
this.$message("这是一条消息");
this.$message({
showClose: true,
message: '恭喜你,这是一条成功消息',
type: 'success'
});
- this.$message(),底层是调用了message函数,message函数里有new实例化的过程:把message的vue文件导出为Main对象,用Vue.extend(Main)构造一个构造器MessageConstructor。在message函数里就是new了MessageConstructor;
- this.$message()带的参数,在Element-UI源码里的message()函数也有处理,讨论了三种类型的参数:字符串、VNode、对象。其中对象的属性根据Element-UI的文档可以传message、type、offset、duration、showClose、onClose回调函数等;
- main.js里面在定义Message的success、info、error、warning四个方法时,有调用Message函数:Message(),说明this.$message.success(...) 这种使用方法,是Element自己调用的Message函数来生成一个实例。
- 但是针对Message本身,main.js里面只有对Message函数的定义,没有调用。说明this.$message是由Vue来调用Message函数从而生成message实例的。
- 关闭message实例的场景有两种:
(1)点击页面上消息弹框的关闭按钮;
(2)手动关闭:比如不是点击页面某个元素触发消息弹框的显示,而是在发请求给后端后需要显示消息弹框,此时想自己手动关闭,用close方法——this.$message.close()
也就是:
main.vue里面的methods里的close方法是给点击关闭按钮用的,
main.js里的Message.close方法是给手动关闭用的。
四、推荐阅读:
感谢阅读~
完~