公共组件设计要点总结
前段时间写了一个列表框架公共组件(虽然是项目内的公共组件,但很多原则和真正的公共组件一样),可以快速灵活搭建各种列表页面,且具有高扩展性,已经完美经历了多个需求迭代。
这也算是我花了很多心思的得意之作了[憨笑],很多设计思路一直想分享出来,总结了一下,抽取一个公共组件,至少应当仔细考虑一下4点:
1、公共组件不应该集成定制性强的功能,可以集成通用或较固定的功能,但应默认关闭那些不常用的功能
只有极少数列表用到的功能就不要封装了,封装他们 增加了公共组件的代码量,但又没有很高的利用率。
几乎所有的列表都可以用到,但不同种类的列表 表现不一样(如内容类别搜索,文章类型的类别,游戏类型的类别 完全不同),就是定制性很强的功能了。
定制性很强的功能封装起来需要包含各种情况,逻辑较复杂,另外也使列表的逻辑分散,不利于维护。试想一下,下次再扩展一个类型,岂不是还要修改这个ListFrame !
(开闭原则:公共组件不能保持稳定的,就是失败的)
这有点小儿科了吧!
实际上识别出哪些功能是通用的(应该封装的),哪些是常用的(应该默认开启的),不能光靠拍脑袋,使用公共组件的过程,不断修正总结 很重要。
维护公共组件,你一定要有一颗开放的心态。组件好不好,由使用的情况说了算,敢于承认失败,才能不断改进。
2、参数的配置要简单且灵活
如果你想让你的组件功能强大,通过配置就能快速扩展,那么一定要避免将配置设计的过于复杂。
配置复杂,无异于将工作量转移到了写配置上,没有任何好处,反而让代码更难理解(额外的 要先理解你的公共组件)。
将配置设计的简单关键就是:用好默认值 和 类型重载,且采用大家一看就明白的名称。
构建列表框架,你必须要考虑分页:
有些列表(如展示全部内容的列表)是没有分页
分页有pageNo和pageSize属性,且初始值是可以设定的,ajax请求时携带的参数名最好也是可以配置的
于是可能你的组件应该这样使用:
<list-frame :showPager="{
show:true, pageNo:{ name:'pageNo', initial:1 }, pageSize:{ name:'pageSize', initial:10 }}"></list-frame>
后台在设计接口时,通常参数名就是 pageNo 和 pageSize;前端使用场景中基本上初始的pageNo是1,pageSize为10。采用默认值,所有的配置都可以省略:
<list-frame :showPager="{show:true}"></list-frame> <!-- 没有传入的字段,就使用默认值,而非空(做好空值适配很重要) -->
为了更简单,再加上类型重载。完整设计代码如下:
class PagerConfig{ show?:boolean, pageNo?:{ name?:string, initial?:string } pageSize?:{ name?:string, initial?:string } } // 类型重载 showPager:Boolean | PagerConfig // 默认值的定义与使用: const defaultPager = { show: true, pageNo: { name: 'pageNo', initial: 1 }, pageSize: { name: 'pageSize', initial: 10 } }; this.pagerConfig = Object.assign( {}, defaultPager, typeof this.showPager === 'boolean' ? { show: this.showPager } : this.showPager ); // this.pagerConfig 就是最终的配置了!
那么使用时,就可以这样写了
<list-frame :showPager="true"></list-frame> 或: <list-frame show-pager ></list-frame>
现在可以看到,一切变的非常简单,同时又保持了足够的灵活性!(灵活性是指,任然可以灵活的配置参数名称 和 初始值)
补充于2020年9月8日15:38:40:
为什么要将pageNo等请求参数名称设置为可配置的?
很多新手在开始写公共组件时,喜欢将字段名设置为固定的:你要用我的组件,必须提供xxx这样的对象!这样设计也许能满足大多数的典型场景,但一旦遇到和你的前置要求有冲突的场景(比如刚好pageNo字段有了别的含义——这很常见,比如在试卷相关的项目中“试卷编号”很可能被命名成pageNo,而分页页码就只能用其他的了),公共组件就歇菜了。
让一切可配置,去掉“要使用我的组件,你必须……”这样的情况!
3、相关的配置要集中
将相关的配置集中在一起,更利于用户使用,可读性更强。
ListFrame 封装了 批量删除 和 批量上下架 的功能。为什么要将这两个功能封装呢?因为:
1、有很多业务无关逻辑值得我们去封装,如disable校验,删除和下架时要弹出输入框,要求用户输入删除原因或下架原因;
2、通用性很强,几乎所有的列表都有删除功能,所有已发布列表都有上下架功能;
3、不同列表除了对应的上下架接口,删除接口不同外,好像没有什么其他不同的。
看看下方代码:
<!-- 集中配置前: --> <list-frame :getPageFn="getPage" :itemsDelAble="true" :delFn="delFn" :shelfOperateAble="true" :offshelfFn="offshelfFn" :onshelfFn="onshelfFn" ></list-frame>
如果开启批量删除 除了设置 itemsDelAble 为 true,还要提供 delFn 删除方法;
如果开启批量上下架 除了设置 shelfOperateAble 为 true ,还需要提供 offshelfFn 和 onshelfFn 两个方法。
既然提开启功能和供方法必须同时存在,为什么不把他们绑定在一起呢?我们将 itemsDelAble 和 shelfOperateAble 有bool类型变成了 object 类型,于是很容易实现了集中配置的目标。
对比一下将配置集中前后的代码,是否后者更容易理解,更方便使用了呢?
<!-- 集中配置后: --> <list-frame :getPageFn="getPage" :itemsDelAble="{delFn}" :shelfOperateAble="{offshelfFn,onshelfFn}" ></list-frame>
(相关配置集中起来,实际上是对你的组件参数,按提供的功能模块进行划分!)
4、避免出现职能交叉的配置
我发现,这一点错误,即便是大型UI框架也会犯。我现在开发的项目所采用的UI框架,关于Button就存在这个问题。下面就用个api截图来说明:(就不发 链接,也不指明是哪个框架了)
按钮的 type 大部分值都是表示主题颜色的,如default,primary,info等,但 text(文本按钮) 和 dashed(虚线框按钮)除外,我觉得这两个应该是属于shape(形状)的值。
这样的 type 和 shape 职能交叉了。现在要实现一个文本按钮,有不同颜色 —— 竟然做不到了!
我们在开发 ListFrame 也存在这样的错误,后面使用时发现后才改正的,这里就不说了,因为上面举的例子已经能很好的说明这个注意点了。
总之设置配置项时,保持高内聚,低耦合的原则是不变的!