本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
目标
了解element-ui中Message组件的写法以及原理
组件
这里我们先了解下Vue中组件是怎么用的,通常都是先注册,后使用。如下面所示
注册
全局注册
// 全局注册
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
局部注册
var ButtonCounter = { /* ... */ }
new Vue({
el: '#app',
components: {
'button-counter': ButtonCounter,
}
})
使用
直接使用注册好的组件名称即可。
// index.vue
<template>
<div id="components-demo">
<button-counter></button-counter>
</div>
</template>
<script>
export default {
....
}
</script>
Vue.extend
如果我们想直接通过方法调用的方式去生成vue实例,可以用Vue.extend来处理。像element-ui中的Message就是通过这个api来实现的。
实现原理
接下来我们来看Message内部是怎么实现的
内容结构
初始化
主要初始化一些公共变量、引入工具函数等,用于维护Message实例等。
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); // 初始化Message组件构造函数
let instance; // 当前Message实例
let instances = []; // 维护 Message 实例
let seed = 1; // Message id
Message函数
const Message = function(options) {
// Vue.prototype.$isServer: 当前 Vue 实例是否运行于服务器。
if (Vue.prototype.$isServer) return;
// 完善options配置项
options = options || {};
if (typeof options === 'string') {
options = {
message: options
};
}
let userOnClose = options.onClose;
let id = 'message_' + seed++;
options.onClose = function() {
Message.close(id, userOnClose);
};
// 创建message实例
instance = new MessageConstructor({
data: options
});
instance.id = id;
// message选项为VNode下的处理
if (isVNode(instance.message)) {
instance.$slots.default = [instance.message];
instance.message = null;
}
// 挂载
instance.$mount();
document.body.appendChild(instance.$el);
// message展示高度间隔处理
let verticalOffset = options.offset || 20;
instances.forEach(item => {
verticalOffset += item.$el.offsetHeight + 16;
});
instance.verticalOffset = verticalOffset;
instance.visible = true;
instance.$el.style.zIndex = PopupManager.nextZIndex();
// message实例添加到 instances 中,后续关掉message用到
instances.push(instance);
return instance;
};
挂载功能函数
// 为Message方法添加 success、warning、info、error 函数
['success', 'warning', 'info', 'error'].forEach(type => {
Message[type] = (options) => {
if (isObject(options) && !isVNode(options)) {
return Message({
...options,
type
});
}
return Message({
type,
message: options
});
};
});
// 关掉指定id的Message
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') {
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,通过遍历 instances 实现
Message.closeAll = function() {
for (let i = instances.length - 1; i >= 0; i--) {
instances[i].close();
}
};
$message实现
通过将上述Message方法注册到Vue.prototype来实现。
测试
这里我们看看Message中是怎么测试组件的,可以从测试用例以及测试工具入手
测试工具
从test目录下的可以看到主要用到的工具 :
Karma
- 简介:一个基于 Node.js 的 JavaScript 测试执行过程管理工具 (Test Runner)。该工具可用于测试所有主流 Web 浏览器,也可集成到CI (Continuous integration) 工具,也可和其他代码编辑器一起使用。
- 使用:通过配置文件的方式,element-ui中的配置文件为karma.conf.js
mocha
- 简介:一个功能丰富的javascript测试框架,运行在node.js和浏览器中。
- 使用:编写对应的describe和it 函数即可。
describe('Message', () => {
afterEach(() => {
// .....
});
it('automatically close', done => {
// ....
});
测试实例
从message.spec.js中可看到测试实例主要针对Message暴露出来的options属性进行单元测试
总结
从这次Message组件中,可以学习到Vue组件是怎样设计的、有哪些实现方式组件、设计完成后怎样对组件的暴露项进行单元测试,从而去保证组件的质量。