前言:
先表一表写这个博客的背景,爱奇艺知识项目的结构之前也是采用组件化形式的,但是遗憾的是,前辈们拆分组件做的并不是很彻底,阅读代码和结构来看,其实是很混乱的,由于结构严重影响了打包的问题,在经历几个迭代后,我们技术部终于忍无可忍,决定动手对组件进行重新整合拆分,一来可以使项目结构更加清晰,二来有利于后续的项目维护和调试,三来也是最重要的,就是解决打包频繁失败报错的问题,毕竟解决眼前的问题才是最重要的。
言归正传,那么先来聊一聊关于组件化的一些理解,后面再结合拆分的例子详细说明下拆分原则与组件间解耦的方式。
- 组件化是什么?
什么是组件化?这个问题是比较宽泛的,答案也多种多样,关于网上大神们的定义和解答我也不再赘述,推荐个知乎上的一个帖子什么叫组件化开发,这里只说一下自己的一点愚见:
组件,其实就是可插拔的一个功能模块。详细说来,就是组件其实就是为了方便快速开发,将一些功能按需做成一个可独立运行,且可以快速集成到项目中的一个功能模块。
它本身体现了设计原则中的高内聚低耦合的特点,这样说可能有些人不明白,那举个例子,登录模块(也可以叫登录组件),对于项目A,有用到登录功能,项目B也用到了,而且一般在一个公司,登录的功能相似度都是非常高的,或者说是一致的。那么如果不用组件化,可能就需要在A,B中写两套登录代码,但如果有登录模块,那么只需要在A,B中分别引入登录模块,传特定的参数即可实现登录功能。以后有项目C,D,E,都可以类似这样实现,登录模块有修改的时候也只需要修改一份代码就可以了。
但是有一点很重要,也是组件拆分中很难的一点,就是对于组件功能的归纳和设计,比如登录模块,你在设计组件的时候,就要考虑到不同APP的可定制化部分,那么登录模块其实只需要实现登录内核相关的部分即可,关于一些灵活的可定制的,可以放到业务层级去定制。
那么根据这个思路解释,组件化其实就是将项目中的功能按分类进行拆分成一个个灵活的基础和功能模块,调研过组件化的都知道组件化大致有基础层,业务层,网络层,路由层等等几个层级,具体项目中的组件拆分,结构并不是一成不变的,我们不能死板地认为组件就那几个层,后面还会继续说到这块。
- 组件化该怎么实现?
关于组件化实现的方式,我理解的是有两种:
2.1. 多target
多target更像是模块化,将功能拆成一个个模块,每一个模块都是一个单独的工程,只是木有做成Pod形式,在使用的时候只需要将所需的target拖到自己的工程项目中,配置好路径即可,这样做的弊端是需要配置路径,开发环境配置较为麻烦。
上图即为多target的形式,具体的实现方式不多做赘述
2.2. pod组件形式
这种就是经常所用的类似第三方pod的形式,使用的时候只需要pod下来就行,比较灵活,下面来简单聊一聊如何用pod组件的方式实现组件化开发结构
我之前的博客如何制作私有库中有说明如何用pod形式制作私有库的过程,博客写的比较详细,使用的是先创建工程,再创建podspec
的形式,下面我简单介绍些如何用pod lib create
的形式一气呵成来制作pod组件库
pod lib create 组件名
根据提示一路选择下来,大致如下:
接下来会看到项目结构如下:
我们所要做的组件内容就应该写到Development Pods
下, 如何让别人也能通过pod下载到你的组件,就要依赖于podspec
这个文件了,此文件中的具体编写在如何制作私有库这个博客中都有介绍,这里不做赘述
到这里,基本上本地的pod组件库的制作就完成了,接下来是如何将你的组件关联到云端,以供别人可用。
第三方的库一般都是公有开源的,公司所用的组件一般是私有的,只有公司内部可用的,一般将源码放到gitlab上,在gitlab上如何创建私有索引库,和私有代码库上述博客也有说明,这里补充一点,如何将本地项目与云端的project结合:
git remote add origin 云端的project(源码)地址
git add .
git commit -m '注释说明'
git push -u origin master
这样就完成了本地工程与云端源码关联
下一步就是将podspec上传到gitlab中的索引管理仓库
首先本地仓库要打上对应的tag值,这个tag值和podspec中的tag值要保持一致
其次podspec上传:
这里有一点要注意,如何制作私有库 此博客中关于 podspec
的验证说的很详细,也附上了对应的常见错误处理,但是如果你制作的仓库依赖了公司的其他库,或者其他的第三方库,可能有 lib lint
失败的情况,这个时候可以略过验证,手动上传(也可写脚本执行上传操作)
说了这么多,总结的几个命令如下:
pod lib create xxx
git remote add origin xxx
git add .
git commit -m 'xxx'
git push -u origin master
git tag tag值
git push origin tag值
上传 podspec
- 组件化的分层到底该如何做?
关于组件化分层,一般是有基础层
,业务层
,网络层
,路由
这几个,但是在实际拆分中会发现,如果严格按照这几个层划分,会很难去定位拆分结构,也有可能出现层级之前的依赖问题,那么关于如何去划分层级结构,我这里做一下简要描述:
大体上的结构还是常用的那些,只不过有一些小小的调整
- 基础层
这里的基础层是那些不常动的部分,比如计算字符串长度,文字颜色转换方法,或者一些常用的工具类,有的文章是将基础层和功能层分开来的,我这里觉得一些常用的工具类也可以作为基础层沉淀下来,前提是此工具类和业务无关
- 网络层
这里与传统的网络层稍有不同,我们常说的网络层是独立存在,可能只需要依赖于基础层和其他第三方即可,但是在实际开发中会发现,我们可能需要额外对请求参数做一些处理,或者对网络请求返回做些处理,那么这时,网络层就可以继续细分为网络核心层与通用网络层(也可以叫通用层)。
网络核心是真正的与其他业务无关的层级,这里的通用网络层可以理解为和网络相关,同时又和业务有关的层级,那么你可能会说,那这个是不是可以放到业务层里面去?当然是可以的,但是如果是类似 pingback
(我们项目有用到,这个主要是数据上报的功能),因为它们有很多相同的地方,如果每一个单独都放到业务层级处理,则会有许多的重复代码出现,所以可以考虑归于网络层中的通用网络层。
在设计层级的时候并不是说一个层就是一个工程(一个组件),一个层可以有多个组件,那这里在设计网络层的时候,就可以将网络核心层与通用网络层分成多个pod组件的形式
- 通用业务层
比如项目中有用到类似微信,QQ这种需要配置一些appId, appKey的,或者是和项目业务有关的配置部分,如常量,通知名,颜色,字体(一般正规开发都会有一套颜色和字体以供使用),以及一些启动后的参数配置, push组件等。
- 业务层
顾名思义,业务层就是一个个的业务功能,比如登录,搜索,首页,我的等等
- 路由
主要是组件间的通信桥梁,一般常用在页面间跳转,传值及回调
其实简而言之,组件分层,并不是一成不变,每一个层可能又可以拆分出基础层,通用层等,但有一点是一定的,那就是每一个基础层一定都是相对而言较为靠下沉淀的,且组件间依赖的顺序必须要遵循上层依赖下层,决不允许有反向依赖的情况,但是在实际开发中会遇到组件间的相互依赖问题,关于这个问题,下个模块会详细介绍.
- 组件化中组件间解耦的方式?
在设计组件的时候,一开始并不能一次性就能设计的很完美,那么就可能遇到B依赖了A组件,C依赖了B组件,但是A又依赖了C组件的相互依赖问题。如果是两三个组件间的相互依赖还好,如果嵌套层级很多,还是从头考虑下自己的组件结构拆分是否有问题吧。
对于这种情况,主要是打破这个循环,那么面向协议的方式就很有必要了。简单来说,就是在两个组件之外有一个处理这两个组件依赖的组件(可以是单独的组件,也可以放到通用业务层的某个组件里面),简单说如下图:
解耦组件中说白了就是协议,A,B两个组件同时依赖与此组件,且遵循实现其中的协议即可
像这里的网络层分为网络组件和pingback组件以及协助这两个组件解耦所用的中间组件,中间组件的实现
在网络组件中的实现如下:
由于原有的代码结构让pingback与网络组件有相互依赖,这样就能实现,在网络组件中对一些参数赋值,在pingback组件中使用到这些参数只需要传递一个遵循协议的id类型参数就可以了。
其他的组件相互依赖的问题,可以用类似的方式解决。
- 路由(注册制)
路由的话题已经有很多博客中提及到,蘑菇街的URL注册制方式,target-action,protocol 协议等诸多方式都可以实现。
我们项目中使用的是注册制,简而言之也就是在项目中有一个plist文件,规定好参数及其对应的值,由后端下发map,去plist中映射出需要跳转的目的落地页(有相关字段对应),将对应的参数打包成一个字典传递过去即可。