源码解析Vue最好用的组件库Element:$message

按:

感觉突然会看element源码了,自己也是长进了。很多前端JD,很多都对组件设计有进一步的要求,学习和研究element对自身成长有很高的性价比。

  1. $message 实现 (已完成)
  2. Form 实现 (已完成)

0 也谈Eleement

目录,口水话。

学就完事了。

Element UI 源码地址,这里我fork了一份,自己用。https://github.com/Otto-J/element

1 目录结构

在正常熟悉源码之前,我们需要显示熟悉目录结构,源码中的大部分结构我们不用去关注。

1
2
3
4
5
6
7
- build 略
- examples 略
- packages 核心,重点
- src 略
- test 略
- types 略。后续ts部分可能会看。
others 略。

因为 src/index.js 实在耀眼,我们先看一下,以 this.$message.error('element 报错提示')<el-input> 为例:

这份代码,称为代码1-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/index.js
import Message from './pageckage/message/index.js'
import Input from './packages/input/index.js'

const components = [Input]

const install = function(Vue,opts={}){
components.forEach(component => {
Vue.component(component.name, component)
})

Vue.prototype.$message = Message
}

if(typeof window!=='undefined'&&window.Vue){
install(window.Vue)
}

export default {
install, // 执行`install`时候挂载组件。
Message,Input
}
1
2
3
// main.js
import MyElement from "./element/index";
Vue.use(MyElement);

为什么说举例这两个组件,相信看了简略版源码就理解了:

  • el-input 代表着标准的组件封装
  • $message 代表着全局挂载

其他的用到再提。这样我们就可以继续关注组件细节了。

补充 Vue.use

使用组件时候,经常通过 Vue.use(xxx) 来引入组件,比如 element router 等。

如何实现的?背后是 install 方法,这个install会自动调用。你负责提供,vue负责执行。

安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。

该方法需要在调用 new Vue() 之前被调用。

我们定义一个最简单的组件:

  • index.js
  • index.vue 里面只写一行 div ,表示组件UI细节。不是重点
1
2
3
4
5
6
7
import MyComponent from './index.vue'
const Bxx = {
install:function(Vue){
Vue.component('自定义组件名', MyComponent)
}
}
export default Bxx

有了这样的组件,我们再 main.js里就可以引入了:

1
2
import Bxx from '/index.js'
Vue.use(Bxx)

这样就可以在vue组件中使用 <Bxx> 这个自定义组件了。

2 Message组件设计细节

用过element UI的相比都清楚,在Vue组件中,使用 this.$message.errro('出现错误‘) 来使用 message 组件,更确切地说,是方法,调用此方法。

我们深入到 以下两个文件:

  • packages/message/src/main.js 书写组件逻辑,封装使用方法,设计使用说明。
  • packages/message/src/main.vue 书写 .vue 文件,设计组件的具体UI表现和功能。

具体的 message 设计思路和使用方法,不再赘述,官方文档是最清楚的。

UI表现

我们先看 vue 文件 Element Message

1
2
3
4
5
6
7
<div :class='type' :style='style' >
<slot>
<p>
{{message}}
</p>
</slot>
</div>
1
2
3
4
5
6
// 当组件 Mount 之后,添加计时器,默认3s之后执行 remove
//这里巧妙地使用transition 的声明周期钩子 @after-leave="handleAfterLeave"
handleAfterLeave() {
this.$destroy(true);
this.$el.parentNode.removeChild(this.$el);
}

因为组件已经比较成熟,所以,配置项比较多,比如自定义提示类型、自定义样式、鼠标事件等,官方还使用了transition。这些不是核心可以略过。

逻辑

1
2
3
4
5
6
import Vue from 'vue'
import Main from './main.vue'
let MessageConstructor = Vue.extend(Main)

let instance = new MessageConstructor()
document.body.appendChild(instance.$el); // 插入DOM

这样DOM元素的显示与隐藏就解决了。


接下来看如何兼容 $message.error('x')$message({type:'',message:''})

我们把 $message视为 Message 函数,这个函数需要兼容 传入对象和传入 .error() 的两种方式。

这就需要我们最终导出的为 Message function。在function上继续挂error等方法。这里官方是把所有类型进行循环挂载。给 options 添加 type属性。

1
2
3
4
5
6
7
8
9
10
11
["success", "warning", "info", "error"].forEach(type => {
Message[type] = options => {
if (typeof options === "string") {
options = {
message: options
};
}
options.type = type;
return Message(options);
};
});

option如何传递?上面代码也提到,如果是字符串直接写入到 message 上。其他不管,无脑挂data

1
2
3
4
5
instance = new MessageConstructor({
// 实例化
data: options
});
console.log(instance) // type message 等都在了。

注意:

还要兼容 vnode写法,也就是兼容 h 渲染函数的写法。允许手写,这时候,就需要Slot插槽。这里先略过,不重要。

1
2
3
4
5
6
7
const h = this.$createElement;
this.$message({
message: h("p", null, [
h("span", null, "内容可以是 "),
h("i", { style: "color: teal" }, "VNode")
])
});

OK。 this.$message就到这里。

参考资料

补充 Vue.extend

Vue.extend(options: Object) 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

请我喝杯咖啡吧~