聊聊如何做好一个开源组件库

1. 背景

前端项目的组件化是为了追求高复用,高可维护性为目的的代码封装,它是前端工程化的一种表现形式。组件化的思想为前端提供了很好的分治策略,开发者们只需要关注以组件方式存在的代码片段,不需要再面对一堆复杂且难阅读的代码合集,单个组件即可以进行独立维护,也可以与其他组件自由组合形成功能完整的界面。 组件的开发过程中,需要开发者将维护性、复用性、拓展性和性能考虑到极致,使得组件能够一处封装、处处使用。根据组件的使用场景,一般我们将组件分为基础功能组件、通用UI组件、基础业务组件等。一些开源的组件库项目,不管是针对移动端还是PC端中后台项目,一般都是提供通用UI组件,那么在通用UI组件库的开发过程中都经历了哪些阶段呢?

2. 组件库开发的各阶段

2.1 准备工作-用户体验的一致性

每个开源的组件库,必然有其固有的品牌基调和品牌倾向性,不同的设计样式会给产品带来不同的调性。

在组件库正式开发之前,开发者应该首先与交互和设计师们对组件库中组件的使用场景和功能进行沟通,由交互师整理出通用的交互规范,以减少用户学习成本、培养用户习惯,从而让组件库交互保持一致性,拥有良好的用户体验。另外,设计师们需制定好设计规范,统一设计样式,保证组件库的视觉风格统一。

比如用户操作反馈,会存在积极文案提示、消极文案提示及长文本提示,统一设计后,用户的操作反馈消息能够很直观的展示。

另外,日期、下拉列表等可选项比较多的选择组件,整个产品应保持一种交互风格,如果一个组件是滚动选择、一个组件是平铺选项选择;一个用户的操作区在模块头部,一个操作区在模块底部,就会使得组件不够友好,用户学习成本增高,用户操作起来会比较烦躁。

2.2 组件库产出过程

2.2.1 组件库设计原则

组件库的设计原则,Front end component design principles总结了八点组件设计需要注意的,并给出了一些代码示例,如下:

1) 层次结构和 UML 类图

2) 扁平化、面向数据的 state/props

3) 更加纯粹的 State 变化

4) 低耦合

5) 辅助代码分离

6) 提炼精华

7) 及时模块化

8) 集中/统一的状态管理

本文对文中部分原则的细节进行解释并补充

1)制定规范

组件库的开发是多人协作的过程,前期如果不制定一套开发规范,百花齐放,后期组件库的维护成本会成倍的增加。

规范的制定应该考虑css命名的统一,比如类库mint-ui样式前缀统一为mint-,iview的样式前缀统一为ivu-{componentname}。这样用户在解决样式覆盖等问题的时候,能够一目了然的知道是哪里出了问题。

另一个需要考虑的是组件对外暴露的方法名或者属性名的统一以及方法名传参顺序的一致性等,在方法和属性的命名上应该考虑兼容性,避免有歧义或者与原生属性冲突。比如有确定和取消按钮的组件,一些定义暴露的方法名为oncancel(instance,cb),onconfirm(instance,cb),一些命名为docancel(cb),doConfirm(cb)等,那么在组件使用中,用户需要不断的翻组件手册,一个个的确定使用方法和传参,使用者的时间不应该浪费在这种组件使用记忆上。关于兼容性,element-ui在之前的一些发版中,因为与原生属性冲突等问题,造成了几次break change,直接影响了用户的开发体验,进而影响了组件的推广。

另外开发者在组件开发中不要炫技,不要过分设计,不要给用户预留彩蛋‘惊喜’,如果团队想给库增加feature,请给用户选择的权利,否则开源项目的信任度会大打折扣,参考antd企业级UI框架的圣诞节事件,HoHoHo~

2)高内聚、低耦合及集中的数据管理

高内聚低耦合是判断设计好坏的标准,很多开发人员在组件封装过程中,设计的组件能够满足使用,但是组件的独立性不高,组件和组件之间的耦合性又不合理,当组件的使用场景变化时又得需要对组件进行改造。开发者在开发组件中应避免组件间相互依赖、共用状态等导致的逻辑理不清,糅合在一起形成一堆乱麻的情形。

下面以出生日期-年龄组件联动组件来大致阐述如何进行耦合及数据集中管理

功能描述:用户在表单中点击某表单项后,出现出生日期及年龄选择组件浮层(动效:蒙层透明度渐变、浮层内容区自底部滚动弹出),用户可以在出生日期或者年龄间进行切换。当用户选择出生日期后,点击年龄tab,年龄列表对应选中当前出生日期的周岁值;当用户选择年龄X周岁时,切换至出生日期tab时,对应选中周岁年的一月一日。用户点击确定时,关闭浮层,根据当前选中tab,暴露不同的值;当用户点击取消,关闭浮层,当用户再次打开组件,直接选中关闭前的tab,列表数据自动定位到上次的选中值。

功能分析:1)该组件包含两个子类型组件,一个是日期选择组件,一个是下拉列表组件,需要开发两个独立组件进行组合

2)日期组件初始化数据包括日期范围[minDate,maxDate]和默认值value,内部集中处理大小月、闰年、日期范围、年月日每列的滚动操作及列之间的联动和数据填充等逻辑,组件暴露每列滚动后的当前日期给外部;

年龄组件初始化数据为日期范围[minDate,maxDate]对应的年龄可选数据列表[minAge,maxAge]及年龄默认值,内部集中渲染年龄列表,组件暴露年龄列表滚动后的当前年龄及出生日期给到外部;

年龄的上下边界minAge/maxAge对应的出生日期01-01是否在出生日期范围及对应的因边界造成的出生日期调整,因为关联两个组件数据应该放在组件外部处理。

3)两个组件的操作区一致,显示与隐藏的动效一致

4)两个组件的选择区位置相同,选择操作一致,两子类型组件间数据相互影响

代码设计:

原组件设计的dom结构:

原组件设计的事件操作:

从dom结构和事件逻辑上看,两个子类型组件内部完成了各自逻辑封装,每个组件有自己的操作区和选择区,组件的初始化数据和暴露的事件也基本保持了一致。但是细看,会发现很多问题:

1)数据管理不集中:组件的数据可以分为初始化数据(未操作过的初始化数据或用户上次点击确认时暴露出的数据)、出生日期和年龄tab切换或滚动选择时的临时数据(只要不点击确认按钮,这份数据就一直保存用户当前操作值)、暴露出的数据(点击确认按钮后的数据露出)。原设计中数据是分开管理的,数据的状态分散在多个事件、多个变量中,当tab点击后,birthObj、ageOb及birthAttrsData、ageAttrsData相互作用,相互影响,如果其中一个数据计算出错,那么出生日期和年龄都存在不一致的可能性。

2)事件管理的不集中:组件的操作区放在了每个子类型组件中,子类型组件再通过事件往父组件进行事件传递,父组件中对子类型触发的事件处理函数是独立处理单个变量的,如果需要排查问题,那么需要在组件事件中层层排查,耗时耗力。

3)dom结构过于松散:组件的蒙层及动效分别放到了每个子类型组件中,操作区结构也很分散,dom的深度及复杂度就增加了。

改进后的代码设计:

dom结构: 蒙层和内容区合并、操作区整合、子类型组件事件统一、事件传参保持一致

改造后的事件操作:


可以看出出生日期和年龄数据集中放在valueMaps中,用户点击出生日期或年龄进行tab切换,用户在内容区进行年龄或出生日期的选择等时机,只需要操作一份数据,该份数据中的年龄和出生日期是保持一致的,排查问题或对数据的操作,只要确定valueMaps中的值正确即可。

3) 辅助代码分离

组件库的组件经过合理粒度划分后,每个组件都包含代码(结构+样式+逻辑控制)、文档、例子、单元测试等内容,在开发过程中,开发者希望能够专注于当前的工作目标,不希望被无关的内容打断思路,分散注意力,那么良好的目录结构和代码分离就显得很重要。在初始化组件库时,编译配置文件、通用工具类(dom操作类、国际化实现、自定义指令、EventEmitter等)、单元测试、组件示例与文档、对外提供的TypeSript声明文件or其他静态类型检查用的文件等进行规划后,项目参与者就可以各司其职,为项目添砖加瓦,项目从长期来看都是可以扩充和维护。

2.2.2 单元测试

一直以来前端代码的单元测试大多都是被忽略的,因为对于前端MVVM框架的应用来说,View层随着需求不断迭代更新,是否进行单测一直是有争议的,但对通用UI组件库来说,它是完全跟业务无关的,组件设计几乎不会产生大的改变,对其进行单测因此是可行的。如果组件库进行开源,对其进行单测改进软件质量也是必须要做的。

前端的单元测试与后端不同的是,前端既有针对类和函数的单元测试,又有针对组件的单元测试。不管是常用的karma+mocha+chai进行的vue单元测试还是Jest+enzyme配合的react单元测试,其单元测试均须覆盖初始化组件、初始化组件的属性、访问组件的数据、调用组件的方法、组件的事件触发、组件内元素的查找等过程。以Vue单元测试为例,其核心是通过@vue/test-utils提供的方法,将组件进行实例化,对渲染后的html输出进行上述验证,以下一段代码示例Vue组件单元测试的各个过程。

2.2.3 文档的产出

在组件库的开发过程中,开发者经常面对两个问题:

1)实例化组件以便于调试

2)为组件生成文档便于使用者了解组件的使用,文档内容包括了组件的引入、组件使用场景demo及源码、组件的[props、events、slots、methods]说明等。

原始的方法一般是手写一个示例页面,对组件的属性、事件等调试完成后,再手动为组件编写文档。这样使得开发者往往耗费很多精力去做这件事情。

幸运的是不管是Vue还是React,均有对应的markdown生成工具供开发者使用,来帮助开发者解决以上两个问题,如Vue中的VuePress、Vue Styleguidist。

对开发者来说,文档也可以当成组件来写,通过docgen工具,配置不同的脚本命令,实现产出markdown文档、html文件或者开启静态网站服务等。

拓展阅读:VuePress、Vue Styleguidist、React Styleguidist、markdown-loader等,感兴趣的同学自行阅读吧,写不动了。

2.2.4 打包发布及版本升级

组件库的发布过程比较简单,配置好package.json文件,执行npm publish即可。这里需要注意的是版本管理,如果每次发布都需要手动进行版本信息的维护,很容易版本号错乱,或者发布过程中因版本过低导致发布不成功等。建议开发者对版本号进行集中维护,如组件库大的功能改造,主版本号需要改变,确定手动修改版本号的,建议给发布脚本传版本参数,通过脚本修改package.json里version字段;如修改一些issues后进行的小的发版,次版本号或修正版本号需要改变,建议通过发布脚本先获取npm仓库中最新的版本号后,根据版本号递增规则,修改package.json里的version字段后发布。

3.总结

文章大致介绍了编写组件库的整个过程及其中需要注意的一些事项,其中还有一些点如ts声明文件的编写、如何去实现国际化、组件分开打包及整体打包、组件的按需加载等因为篇幅问题,暂未介绍。开发者在开发过程中,可以一点点将各项加入组件库,最终产出比较完善的开源组件库项目。

作者:Y女王

链接:https://juejin.im/post/5ed4d7c1f265da77172029a9

你可能感兴趣的:(聊聊如何做好一个开源组件库)