组件化是长期开发过程中一个提炼精华的过程,目的主要是以下几点:
- 提高复用性
- 解耦
- 提升未来的开发效率
那么如何达到这样的效果呢,我们可以分几步来循序渐进地完成。
下文主要是思路,想直接获取代码可转战ElementUI的Github去看源码
一 组件的定义
组件从大类上可以分为两种:
- 基础组件:例如ElementUI
- 业务组件:通过基础组件或者业务组件组合而成,与业务强相关甚至强绑定
二 组件的颗粒度
基础组件的颗粒度争议并不大,就是Button,Table等等
业务组件的颗粒度最大可以为一个feature,一个feature就是一个可以独立上线特性,例如文章的评论点赞功能。我们可以想象有一个开关,打开就有这个feature,关闭就没有,且不会造成联动的影响。
三 组件的接口
- Vue的组件基本都是通过属性来进行配置,进而控制组件的功能变化。因此开发组件之前我们就得明确定义变量是什么,明确组件需要开放的接口
- 我们需要理解每一个组件的核心功能是什么,可通过单一职责这样的设计模式来考虑。组件的核心功能是不能发生变化的,这也是为了接口的向后兼容
- 对于业务组件,我们可否直接把一些常年不发生变化的数据和UI绑定在一起,从而删减部分接口。大家不必担心耦合性,解耦的前提是有被解耦的需求。
四 开发组件
1 UI规范
UI规范是组件开发的物理依据,你得知道要做成什么样子,你才能做。UI规范要对整套组件的各个视觉元素(长,宽,padding,margin,圆角,颜色,字体,字号,边框,图标,阴影)有语意明确的定义和友好的标注,这样前端工程师们才能做到有法可依。
2 开发
举例:
1.sass或less来写样式
2.重置样式文件和样式的公共变量文件(将视觉元素翻译成代码中的常量形成的文件),这是根据自己的UI规范来决定的
3.ES6的哪些特性不使用,用哪一个stage的babel来翻译
4.需要引入的第三方包有哪些,这个也决定了最后打包出来组件js的大小
5.工程的目录结构
3 打包
对于Vue,我们通常使用的是webpack。具体配置这里不详细讲解,可以参考入门Webpack,看这篇就够了,还有一个快速方法就是通过Vue-cli生成的模板工程来进行更改。
接着我们要定义清楚我们的打包策略:
举例:
1.所有的样式打到一个文件
2.有fonts则单独打出来
3.组件JS和VueJS不打在一起
4.非通用的第三方包需和组件打在一起
五 最佳实践
以上四点是正式编写组件代码的前置工作。现在我们通过elementUI的源码来看一个最佳实践,我们的例子是比较简单的面包屑,先看一下怎么去使用的这个组件:
然后我们来看一下代码如何实现的
<template>
<span class="el-breadcrumb__item">
<span class="el-breadcrumb__inner" ref="link" role="link">
<slot></slot>
</span>
<i v-if="separatorClass" class="el-breadcrumb__separator" :class="separatorClass"></i>
<span v-else class="el-breadcrumb__separator" role="presentation">{{separator}}</span>
</span>
</template>
<script>
export default {
name: 'ElBreadcrumbItem',
props: {
to: {},
replace: Boolean
},
data() {
return {
separator: '',
separatorClass: ''
};
},
inject: ['elBreadcrumb'],
mounted() {
this.separator = this.elBreadcrumb.separator;
this.separatorClass = this.elBreadcrumb.separatorClass;
let self = this;
if (this.to) {
let link = this.$refs.link;
link.setAttribute('role', 'link');
link.addEventListener('click', _ => {
let to = this.to;
self.replace ? self.$router.replace(to)
: self.$router.push(to);
});
}
}
};
</script>
props中就是ElBreadcrumbItem暴露出来的两个接口,也就是上文第三点提到的内容,接口的值是从父组件传过来的。我们再看一下ElBreadcrumb,也就是父组件的代码实现。
这里简单解释一下inject:inject和provide是成对出现的,是vue@2.2.0的新特性。通过此种方法变可以直接调用提供provide的组件中的属性了,总结就是依赖注入(DI)。
<template>
<div class="el-breadcrumb" aria-label="Breadcrumb" role="navigation">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'ElBreadcrumb',
props: {
separator: {
type: String,
default: '/'
},
separatorClass: {
type: String,
default: ''
}
},
provide() {
return {
elBreadcrumb: this
};
},
mounted() {
const items = this.$el.querySelectorAll('.el-breadcrumb__item');
if (items.length) {
items[items.length - 1].setAttribute('aria-current', 'page');
}
}
};
</script>
slot其实是专门留给ElBreadcrumbItem的插槽,实际上ElBreadcrumb不涉及什么UI,它也通过props暴露出来了两个接口,这个两个的值是使用者传入的。我们可以看到,默认的分隔符是'/',如果你在ElBreadcrumbItem中通过属性传入的分隔符是'+',那面包屑每一级中的分隔符也会是'+',注意一下代码中的provide和上面的inject相对应。最后就是让组件可注册
import ElBreadcrumb from './src/breadcrumb';
/* istanbul ignore next */
ElBreadcrumb.install = function(Vue) {
Vue.component(ElBreadcrumb.name, ElBreadcrumb);
};
export default ElBreadcrumb;
通过给组件添加install方法,让组件可被Vue.use方法在全局注册。可参考Vue官方文档API
总结
本文简单梳理了一下组件开发的思路,重点在于组件开发的这些前置条件:
- 定义组件
- 划分颗粒度
- 理清组件的核心接口
- 如何定义打包策略和UI规范
完成这几点,从代码层面只是一小部分工作,但若把整个组件作为一个产品来看,就已经完成了一半了。