随着浏览器性能提升,新的技术和形式不断升级,前端开发工程师面对的交互和设计也日趋复杂,开发周期变长,旧有的项目维护困难,与新的功能难以兼容匹配。前端开发者也需要学习和使用传统软件开发的思路和工具。其中组件化是最早在前端范围内展开应用的开发思想,比如 Bootstrap 对于标签样式的规范就是一种前端组件化。本篇文章是基于 Angular 框架对前端组件化的一些实践和思考,如有纰漏,还请谅解,欢迎指正。
目的一:复用
将部分模版和逻辑封装为组件,并在不同的页面位置中使用,可以实现代码的复用,同时保证页面展现和逻辑的统一。
如上图所示,红框中的组件分别被使用在商城首页和购物车相关推荐位,这两处的数据结构和样式完全满足复用的条件,故使用统一开发的组件,实现了前端代码的复用,便于维护。
目的二:理清开发思路
前端开发者在接到设计和产品提出的需求后,按照组建拆分的思想,将任务有效的细化,可以降低开发难度。拆分后的子组件复杂度更低,职责单一,也更有利于我们评估开发周期,控制研发风险,保证项目进度。当然能够有效的拆分需求,并构建组件树,需要开发者对需求和设计的理解非常透彻,这也变相的要求我们在开发之前要理清思路。
目的三:易于扩展
上面红框中的两个组件实际上是基于同一个公共组件进行二次开发的。这两个部分有相同的轮播和横向滑动切换的交互需求,所以使用了同一个轮播组件作为容器,再分别定义内部的子组件。两个组件有效的继承了容器组件的轮播和横向滑动的功能,又加入了各自的自定义需求。由此可见,组件化是前端继承的一种实现。
目的四:易于测试
封装后的组件,与页面其他功能解耦,同时有明确的组件接口定义,便于单独做测试和集成。上图中的模态框组件,定义的输入参数有:标题、显示状态、按钮文案、隐藏取消键、文本居中、确认按钮校验,自定义事件:用户确认、用户取消。
在明确了组件化的目的后,下面来总结组件化的实现。
从需求评审、产品定义原型、设计出图,到进行开发,测试,交付,组件化的思维应该贯穿每一个协作的部门人员。
产品定义阶段
需求评审阶段,就应该为产品经理和设计师提供现有开发完成的组件,尽可能设计能够复用现有组件的产品原型;
开发前
接到设计图后,拆分构建组件树,设计细化后的子组件,根据子任务创建开发里程碑,评估开发周期。为每个组件设计输入输出接口,接口需要设计成由基本数据类型构成的接口对象。组件树、接口类、子任务功能构成开发文档的基础,并在开发前交付团队其他成员 review。
可以看出,组件化的定义需要在开发前进行精心设计推敲,才能保证开发过程的顺利、可控。
上图是移动端商城首页的组件列表,因为首页楼层模版较多,所以拆分出了很多子组件。
当组件的接口数据结构比较复杂,应该将接口的数据结构抽离封装成一个接口类型,这样便于引用和校验,保证组件与组件的使用者通讯时的数据一致性。
组件的拆分原则
1、在设计图中,相同的展现和交互重复出现的部分可以拆分为组件;
2、模版类型的开发需求;
3、预估开发周期较长、结构复杂的组件,即使复用的可能性较低,也可以拆分来降低开发的复杂度;
组件间通讯
在 Angular 框架体系内,组件间通讯主要有两种方式,一、通过 input & output 通讯,需要组件为父子关系;二、通过注入单例 service 实现双向通讯。
例:移动端的公共 Header 被使用在最外层的 app.component 中,使用时会有很多子模块需要对 Header 组件中的状态做修改,或响应 Header 中触发的事件(如:购物车 Header 中的“编辑”&“完成”状态的切换),此时通过 header.module 模块注入的 HeaderHandlerService 完成状态的传递。Service 通讯机制允许多对一的跨多级组件通讯。
公共组件的模块化封装
基于 Angular 框架的组件封装,一个组件只能声明在一个 Module 中,公共组件的定义意味它有可能在多个 Module 中使用,因此在公共组件创建时,将组件及相关依赖封装在一个 Module 中,并将组件暴露出去,便于其他 Module 引入。
公共指令、管道、服务、动画
组件化的思想同样可以应用在指令、管道、服务、动画中,凡是有复用场景的功能都可以抽离,并用模块封装为独立的作用域。
目前工作中遇到一些仍需改进的组件化问题,仅此抛砖引玉之效:
1、上文组件化实现是基于 Angular 框架实现的,如果跨框架、跨端如何实现组件和功能的复用?
2、组件的测试,组件集成的测试,如何避免组件共用和嵌套的风险;
3、组件的持续开发和扩展,老旧组件的维护;