一、基础理论
1.什么是组件?
1.1组件化的定义
将实现页面某一部分功能的结构、样式和逻辑封装成为一个整体,使其高内聚,低耦合,达到分治与复用的目的。
PS:高内聚、低耦合意思就是:哥,我想按时回家哄妹子!!!你怎么写代码我不管,你的功能全在这你这儿实现(内聚性),不要让我还帮写你那块功能。另外,哥,求你了,你代码不要block(影响)我的代码(低耦合性)。有了这个特性,才能提高协同合作的效率。
在前端范畴,我们可以用下面的这张图来简单地理解组件化:
这样看起来,组件化前端开发就像造一辆车,我们将轮子、发动机、悬挂、车身车门等等各部分组装成一辆车,轮子、发动机就是组件,车就是最终产品。我们将页头、侧边栏、页脚、内容区等等组件拼装起来组成了我们的页面。
1.2组件化的意义
分而治之
在谈到组件化的意义时,很多人的看法都是组件化的目的是复用,但我并不赞同这一看法。
良好地组件化以后的组件,会表现出高内聚低耦合的特征,这会给我们带来好处:
- 组件之间不会相互影响,能有效减少出现问题时定位和解决问题的时间
- 组件化程度高的页面,具有清晰的页面组织和高可读性的HTML结构代码,组件之间的关系一目了然
- 组件化会强迫开发人员划清各个组件的功能边界,使得开发出的功能更加健壮
- 组件的划分直接的带来了代码的拆分,减少了庞大代码文件的出现,让代码更加简洁、直观,目的性、功能性更加明确。
所以分而治之才是组件化的意义所在,复用只是它的副作用。同时我们还有很多其他方式都可以做到复用,这并不是组件化的专利。
1.3组件的特性
- 高内聚:我们将相关的一些功能组织在一起,把一切封装起来,而在组件的例子中,就可能是相关的功能逻辑和静态资源:JavaScript、HTML、CSS以及图像等。这就是我们所说的内聚。这种做法将让组件更容易维护,并且这么做之后,组件的可靠性也将提高。同时,它也能让组件的功能明确,增大组件重用的可能性。
- 可复用(不是必须特性):功能明确,实现清晰,API易于理解。自然就能促进组件复用。通过构建可重用组件,我们不仅保持了 DRY(不要重复造轮子)原则,还得到了相应的好处。 这里要提醒: 不要过分尝试构建可重用组件。你更应该关注应用程序上所需要的那些特定部分。如果之后相应需求出现,或者组件的确到了可重用的地步,就花一点额外时间让组件重用。事实上,开发者都喜欢去创造可重用功能块(库、组件、模块、插件等),做得太早将会让你后来痛苦不堪。所以,吸取基于组件开发的其他好处,并且接受不是所有组件都能重用的事实。
- 可互换:一个功能明确好组件的API能让人轻易地更改其内部的功能实现。要是程序内部的组件是松耦合的,那事实上可以用一个组件轻易地替换另一个组件,只要遵循相同的 API/接口/约定。
- 可组合:基于组件的架构让组件组合成新组件更加容易。这样的设计让组件更加专注,也让其他组件中构建和暴露的功能更好利用。不论是给程序添加功能,还是用来制作完整的程序,更加复杂的功能也能如法炮制。这就是这种方法的主要好处。
1.4组件化与模块化
模块化是一种处理复杂系统分解成为更好的可管理模块的方式。它可以通过在不同组件设定不同的功能,把一个问题分解成多个小的独立、互相作用的组件,来处理复杂、大型的软件。
这段话出《Java应用架构设计》,似乎在后端领域,组件化和模块化说的是同一件事。但在我的理解中,前端领域的组件化和模块化是两个概念。先说结论
组件化是从产品功能角度进行分割,模块化是从代码实现角度进行分割,模块化是组件化的前提和基础。
当我们将一段代码写成一个模块的时候,它有可能是一个函数、一个对象或者其他什么做了一件单一事情的东西,我们将它做成模块是因为它完成了一个单一的功能,并且这个功能很多地方都可能用得到。
而当一个组件被从产品中抽象出来,它有时候就只是一个模块,但有时候却有相对复杂的实现,它就可能会有多个模块。
我们说一个日期选择器是一个组件,但实现它的时候,我们分成了计算模块、渲染模块、用户输入响应模块等等模块来实现。一个单一产品功能的实现,可能是由多个模块来实现的。这样理解起来,其实可以说组件化是更粗粒度的模块化,它是在产品功能上的模块化。说到这里,其实不难理解为什么后端领域可以认为组件化与模块化是一件事了,这一点交给大家思考。
二、实践经验
1. 组件层级划分经验
依据与业务的耦合程度,由低到高,我们可以将组件分为三个层次:UI组件,应用组件和业务组件。
UI组件主要是大部分由UI库提供的业务无关的纯UI渲染组件,三者中它的粒度最细,每个组件就完成一个UI功能;同时因为无关业务它可以在项目间具有通用性。
应用组件则是与业务有一定耦合的组件,它是基于UI组件进行的封装或组合,粒度与UI组件类似,但带上了一定的业务属性,仅在本项目通用。
业务组件则是完成某个具体业务的组件,它是基于UI组件和应用组件进行的封装或组合,粒度最粗,具有针对性的业务属性,它不需要也不具备通用性。
反映到实现中,可以用一个例子来理解:列表组件 -> 用户列表组件 -> 用户管理组件。基于这种分层,从文件组织,到组件划分,都会有一些最佳实践。
- 适度的组件嵌套:a->b->c->d->e->f...当嵌套层级过多时会带来另一个极端,复杂度不降反升。合适的嵌套规则应该是UI组件尽可能相互独立,不进行嵌套;应用组件是最容易发生过度嵌套的地方,所以它们之间也应该尽可能互相独立,即使嵌套也请不要超过1层,它们应当纯粹由UI组件和业务规则组成;业务组件则仅仅应当由UI组件和应用组件组成,不应该在一个业务组件中嵌套另一个业务组件,这会让业务逻辑显得很奇怪
- 良好的组件命名:UI组件的名称应当反映组件功能,应用组件的名称应当反映业务属性和组件功能,业务组件名称则应当完全体现业务属性。(全组件化时组件命名会是一个比较头疼的问题,但可以参照一些命名规范来解决)
-
统一的组件接口:组件的接口命名应当表达一致的语义,类似
message
、text
、items
这样常用的接口名称代表的语义和功能尽可能要在项目中得到统一。(项目采用ts编写可以有效解决这一问题) - 清晰的文件组织:UI组件应当来自项目中引入的UI库,或者项目中单独的UI组件文件夹,应用组件应当来自单独的应用组件文件夹,而业务组件则应当每个业务组件一个文件夹,在其中存放该业务组件相关的一切文件
PS:当我们按照上面的划分来组织组件的时候,还会面临一个问题,一个业务组件中,并不完全是由UI组件和应用组件组成的,很多部分其实并不具有任何通用性,那这部分应该如何处理?通常情况下我们会直接将它们写在业务组件中,所以我们一般见到的业务组件多是自定义组件和原生HTML代码混杂在一起的。但更优雅的解决方案,是将这部分内容也拿出来做成组件,它们就放置在业务组件自己的目录中,一旦你这样做,你会发现你的业务组件中不再出现大块的原生HTML代码,取而代之的是语义清晰结构简明的自定义组件(如上图)。组件化的首要目的是分治而不是复用,所以即使没有复用的需求,你也应该有动力去进行组件化。
2.遇到的问题
2.1 标签语义话代价太大
组件化务必要给组件定义标签。只要用了标签,就一定需要给它合适的语义,也就是命名。随着组件的细化,组件的数量随之增长,组件的命名也将会更加困难。可以参考组件命名规范以及Vue风格指南进行命名
实际用的时候,很可能只是为了把一堆html简化一下而已,到底简化出来的那东西应当叫什么名字,光是起名也费不知多少脑细胞。
2.2 ajax是否需要置于组件内
大量的刚刚开始进行组件化的团队成员们都会对一个问题进行争论:ajax是否需要封装到组件内部?
先说结论:不需要也不应该。原因很简单:解耦。
仅考虑两种情况:
一个应用组件在某个业务组件中引用了两次:当这个应用组件内部在created钩子中封装了加载数据请求的ajax时,如果参数相同,那么该组件的请求会在同一个业务组件中被发送两次
项目需要进行统一的ajax管理和优化:当组件内部存在ajax逻辑的时候,统一的ajax管理和优化会变得麻烦
解决方案:
那就是引入一层Store的概念,每个组件不直接去到服务端请求数据,而是到对应的前端数据缓存中去获取数据,让这个缓存自己去跟服务端保持同步。所以,在实际做方案的过程中,不管是基于Angular,React,Polymer,最后肯定都做出一层Store了,不然会有很多问题。
3.总结
全组件务必会对开发人员的要求更高,开发时务必要考虑更多的因素,从而占用更多的开发时间。
在项目初期,要考虑的东西太多了,而且为了赶进度工作量也比较大,一切为了开发效率为主,但是一个好的编码规范、编码思想无疑会给项目后期的维护带来无穷大的好处。
就像文章开头说的,如果你想按时回家哄妹子...不用管别人写的庞大的代码(项目后期代码量一定会越来越大),只专注于自己的业务,不想改两行代码都有种牵一发而动全身的感觉。Then, 理解高内聚、低耦合,让开发者写出来的代码(组件)可以像拼乐高那样,随意组合替换亦或是复用。
希望大家在以后的编码过程中,可以参考甚至是改进这种组件划分的方式,写出更加高质量的代码出来。