组件化是长期开发过程中一个提炼精华的过程,目的主要是以下几点:
提高复用性
解耦
提升未来的开发效率
那么如何达到这样的效果呢,我们可以分几步来循序渐进地完成。
下文主要是思路,想直接获取代码可转战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的源码来看一个最佳实践,我们的例子是比较简单的面包屑,先看一下怎么去使用的这个组件:
用法.png
然后我们来看一下代码如何实现的
<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规范
完成这几点,从代码层面只是一小部分工作,但若把整个组件作为一个产品来看,就已经完成了一半了。
作者:PengL
链接:https://www.jianshu.com/p/dc83b40bc63c