2025上半年,最新 AI实践都在这!20+ 应用案例,任听一场议题就值回票价 了解详情
写点什么

编写高质量可维护的代码:组件的抽象与粒度

  • 2021-07-30
  • 本文字数:4263 字

    阅读完需:约 14 分钟

编写高质量可维护的代码:组件的抽象与粒度

前言


作为一名精致的前端猪猪女孩,也有那么点想让自己的代码同样看起来精致一点。所以在拿到新需求的 UI 设计稿时,经常会面临如下问题:如何拆解页面?如何划分组件才算是合理?好像用于组件拆分的 A 方案和 B 方案在当前业务场景下也都还算合理,那究竟要怎么选择?组件的抽象与粒度貌似是一个老生常谈的问题了~学习了很多前辈的文章,那么今天结合业务场景,也来讲下我的心得~


什么是组件


React 官方文档 (https://react.docschina.org/docs/components-and-props.html)上说:组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。


Vue 官方文档 (https://cn.vuejs.org/v2/guide/components.html)说:组件是可复用的 Vue 实例,且带有一个名字。我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用。


其实总的来说,无论什么语言框架,组件就是一段代码片段,它可以实现某些指定的功能或渲染特定的展示效果,我们一般可以通过 import 的方式将其引入到项目代码中。本文接下来将以 React 为例进行相关描述。


组件的抽象


组件抽象的过程就是将通用性代码“提取”或是“抽取”出去的过程,那么问题来了,我们为什么要抽组件呢?


为什么要抽组件


说到为什么要抽取组件,不知道各位读者有没有遇到过一个 js 文件中有 1k+ ~ 2k+ 行 React 代码,甚至更多行代码的情况。这种情况往往导致代码难以维护,当有新的需求涉及相关改动时,在一定程度上增加了代码的学习成本(特别是当你刚刚新接手了一份完全不熟悉的项目的时候)。


其次,某些情况下,有一部分代码在不同场景下其实是可以复用的,例如新增和编辑的弹窗,可能只有弹窗的标题和某些字段有部分差异,此时没必要把高度相似的代码复制两遍,增加代码的冗余。



因此,在我们日常开发中,组件抽取是有必要的,其目的在于代码的分层复用,降低项目的复杂度。


组件抽象的基本原则


单一性


单一性要求一个组件具有高内聚,低耦合的特征,它只负责一件事情,不要耦合一些没必要的逻辑,并且尽量不要和其他组件有过于多的双向交互和互相依赖关系。单一性并不代表着不可以引用其他组件,当前组件可能是外层的容器组件,里面包含一些子组件,这样的设计是没问题的。


复用性/通用性


在设计组件的时候,一定要考虑组件的复用性或者说是通用性。这是指,当组件封装好后,可以在类似的使用场景中直接调用。这要求我们在设计组件的时候,考虑组件功能的通用性,以及考虑组件入参的合理性。


此时有两种情况:


一种是很多不同的项目间,可能存在类似的使用场景,因此会提炼出一个公共的组件,为了复用。一般我们称之为基础组件或业务组件,姑且叫它公共组件吧。


另一种是在项目内部,仅在当前场景下作为一个独立的模块可以抽取出来作为一个组件,暂时称之为项目组件。


公共组件和项目组件在设计上的侧重也有所不同,公共组件要更多的考虑通用性,通过一个组件满足不同项目中相似的使用场景,比如 AntD 基础组件库。而项目组件更多的是处理当前业务中的特殊场景,可能是页面拆解后的不同模块,也可能是不同操作的弹窗,往往这种组件不适合直接“移植”到其他项目中使用。


然鹅,对于一个组件来说,个人认为也不能一味的追求通用性使其变得难以维护。例如,当遇到下述页面的时候,要如何抽象组件呢?



不难发现,页面中交易方式、基础配置和合同设置这三个模块其实是具有一定共性的,全部呈现为列表形式,只是在某些列上有展示差异。前辈的做法是,考虑了所有情况,抽象成一个组件。通过 title 区分模块名称,由于仅在交易方式模块有操作列,因此通过 areaCode 区分当前页面下的不同模块等。


<TableConfiguration  // 基本参数  title="基础配置" // 标题名称  data={baseSettingData} // 展示数据  areaCode="baseSettingConfig" // 模块 code  config={baseSettingConfig} // 一些业务逻辑参数  // 新增参数  pageId={this.pageId} // 当前页面 Id  userIdentity={userIdentity} // 用户身份/>
复制代码


在业务发展前期,这样抽取的组件的确使用起来很方便,且通用性很强。但随着业务的膨胀,同一项目中不同页面开始出现相类似的模块,于是新增 pageId 标识,用于区分不同的页面以及对应页面的特殊逻辑。又过了一段时间,新增 userIdentity 标识,用于控制不同登录用户对页面的查看或操作权限。


长此以往,新增的参数越来越多,组件内部开始出现大量的判断逻辑,尽管这个组件通用性很好,能应对各种页面展示逻辑,但这也使它本身变得逐渐难以维护。还有一种比较好的解决方案是通过表单中心生成一份这样的页面,可参考本团队之前的一篇文章《动态化表单设计》。


分离处理


师父曾教导我说抽组件最好做一下业务层和视图层的分离处理,其中视图层主要负责页面展示样式和交互,业务层主要负责处理业务逻辑,比如接口调用,数据结构调整等。这样做的好处除了职责分离,还可以有效提高组件性能(比如视图层可以用 PureComponent 处理)。


另外,例如上述的新增和编辑弹窗,当新增和编辑两个操作需要分别调用不同接口时,业务层和视图层的分离处理可以避免组件中耦合对“新增”或“编辑”的判断,它们可以共用一个视图,并在各自的业务层实现不同的业务逻辑。


组件分类


业务组件 vs UI 组件


业务组件侧重于数据和业务的逻辑处理,其中数据一般通过接口获取。目前本团队维护的业务组件库,可以使开发人员即来即用,组件内部有完善的功能和接口数据处理,将组件引入到项目后可直接实现对应功能。


UI 组件一般也可以称为基础组件,它们经常在多个地方被复用,且不耦合任何的业务功能,例如:AntD 组件库。UI 组件侧重于页面展示效果,大部分 UI 组件具有原子性,一些复杂的 UI 组件可以由基本的 UI 组件构成。一般情况下组件内部的数据来源于父组件传递过来的 props。


纯组件 vs 非纯组件


有一天,我看到前辈大神这么写的代码


export default class NotFound extends PureComponent {  // 此处省略具体代码}
复制代码


于是去学习了下纯组件和非纯组件的区别,首先让我们了解下 React 中的各种组件 (https://zhuanlan.zhihu.com/p/30659051)一文中对 React 组件重新渲染机制的描述:

一般当一个组件的 props (属性)或者 state (状态)发生改变的时候,也就是父组件传递进来的 props 发生变化或者使用 this.setState 函数时,组件都会进行重新渲染。


而在接受到新的 props 或者 state 到组件更新之间,其实会执行生命周期中的一个方法 shouldComponentUpdate,当该方法返回 true 时才会进行重渲染,如果返回 false 则不会进行重渲染。


纯组件和非纯组件的区别在于,一般情况下非纯组件并未自动实现 shouldComponentUpdate 方法的功能(但可以手动调用这个钩子),而纯组件中利用 shallowEqual 的方法对 props 和 state 做浅比较实现了该功能。实际应用中,纯组件一般用于纯展示型组件,相对于非纯组件来说,减少了手动判断 props 或者 state 变化的繁琐操作。并且,纯组件可以通过减少 render 调用次数来降低性能损耗,但是使用过程中也一定要确保此类组件的渲染仅取决于 props 与 state。


非纯组件的话,其实我们日常开发中比较常用。一般情况下,在不做特殊处理时,正常 extends Component 出来的组件都可以认为是非纯组件。


export default class MyComponent extends Component {}
复制代码


我们可以根据实际的开发场景选择继承自 PureComponent 还是 Component。值得注意的是,由于纯组件中做的是浅比较,因此带有深层嵌套的数据是对比不出来的,请慎用~


组件的粒度


提到组件的粒度,大多数人的第一反应可能认为拆分的越细越好。但是,这样一定是最优解嘛?个人认为其实不是的。


组件拆解的过于细致可能导致某些参数从父组件开始一层层向子组件传递,容易漏传,错传,或者其中某层组件忘记判空的时候,可能会导致页面报错。虽然可以通过 React Context 去获取,不过好像还是“徒手传递”的人更多一点。但组件如果拆解的太粗略往往也会导致复用率低、难以维护等问题。


讲到这里,让我想到了 原子设计 (https://atomicdesign.bradfrost.com/)。原子设计是 Brad Forst 于 2013 年提出的设计概念,该作者用 5 个层级来描述组件库的设计。做下类比,映射到开发人员使用和熟知的组件中,个人认为也适合描述组件粒度。



  • 原子组件


如果说,原子是物质的基本组成部分,那么原子组件就可以作为构成我们所有页面的最基本组成部分。原子组件,可以为上文中提到的基础 UI 组件,例如一个 Input 或一个 Button。它们往往具有不可再拆分的特性,是其他组件的基础。


  • 分子组件


分子组件一般由几个简单的原子组件组成,比如由一个 Label 和一个 Input 组成的姓名输入组件。这种粒度的组件初步具有一定形态和自身属性,与原子组件相比,有一定的可操作性。



  • 生物组件


生物组件是由原子组件及分子组件组成的相对复杂的构成物,它是一种作为一个单元发挥作用的集合体。比如由姓名输入组件和一组按钮组成的搜索组件。在这个组件中,姓名输入组件被放置在一种使用环境中,实现了简单的功能。



有些生物组件是由不同的分子组件构成,但也有可能由相同的分子组件构成,比如网站首页的商品展示组件,该组件由六宫格组成,每个格子使用同一个分子组件进行渲染和展示。



  • 模板组件


模板组件是由原子、分子、生物组件按照一定布局结构组成的区块。它们专注于页面的基础内容结构,而不是页面的最终内容。模板组件是更复杂一点的生物组件,更多的赋能于功能和展示。



  • 页面


最终,通过不同模板组件的拼装,可以生成一个完整的页面。


在实际应用中,组件设计时的粒度往往也需要依据具体的场景具体分析,但原则可以参考高内聚,低耦合的思路,使自己的组件易于维护,同时使自己的整个项目代码看起来干净利落。


总结


其实,本人真心认为组件的抽象与抽象粒度这件事,没有一个一成不变的统一标准,也没有对与错。在基本原则不变的情况下,更多的应该去关注如何适配不同的业务场景和需求要求,求的是“适合”。有时,同样的场景,组件粒度的标准也会随业务场景变化而变化,甚至可能随场景而持续重构。不过为了代码更好的维护和分层,以及避免代码逻辑的过度叠加和膨胀,团队中可以制定一些组件抽象的规范稍稍加以约束。



头图:Unsplash

作者:鱼鱼

原文:https://mp.weixin.qq.com/s/6U8zMpnBk9nBI_bQAobdfw

原文:编写高质量可维护的代码:组件的抽象与粒度

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-07-30 21:003748

评论

发布
暂无评论
发现更多内容

“程”风破浪的开发者|C#下WPF中实现贪吃蛇小游戏(超详细步骤附带源码)

木偶

C# 学习方法 贪吃蛇 10月月更 “程”风破浪的开发者

训练大模型的九大深度学习库;谷歌转向文字→视频生成的两大利器|AI系统前沿动态

OneFlow

人工智能 深度学习

Vue3组件开发之:父子组件之间的通信方式🔥

渔戈

前端 Vue3 10月月更

未来智安2周年 | 行则将至,未来可期

未来智安XDR SEC

华为云加固企业网站安全堤坝,助您业务稳定流畅

科技之光

js裁剪(分隔)字符串常用方法

木偶

JavaScript 前端 10月月更

Linux网络-HTTP协议

可口也可樂

Linux 网络协议 HTTP 10月月更

算术四则混合运算表达式的求值

可口也可樂

算法 10月月更 四则运算

嵌入式 Linux 入门 环境篇(一、开发板初体验)

矜辰所致

Linux 开发板 启动流程 10月月更

华为云网站安全解决方案,站在企业安全的最前沿

科技之光

我摊牌了!真正的灰度队列实现方案!全网你都搜不到!

艾小仙

Java kafka RabbitMQ 中间件 计算中间件

鏖战 48 小时,TiDB Hackathon 都诞生了哪些硬核创意?

PingCAP

TiDB

C++中的四种类型转换

可口也可樂

c++ 类型转换 10月月更

大咖说·小码王|人工智能时代,如何让孩子掌握与机器交流的语言?

大咖说

人工智能 阿里云 降本增效

华为云焕新数字生活,打造情景化智能产业链条

爱尚科技

华为云

「云渲染」渲染农场的架构原理、特点

Finovy Cloud

架构 原理 云渲染 云渲染农场

蒙牛前数科部总监刘瑞宝分享蒙牛数字化转型成果-星策社区大咖说(一)

星策开源社区

决策 智能化转型 数据链路 蒙牛

TCP/IP协议中分包与重组原理介绍、分片偏移量的计算方法、IPv4报文格式

Python-派大星

10月月更

华为云同步科技信息端,海量存储性能

爱尚科技

华为云

一文带你走进C++【内存泄漏】

C++后台开发

c++ 后端开发 内存泄漏 Linux服务器开发 C++开发

JavaScript刷LeetCode心得

js2030code

JavaScript LeetCode

个人和初创企业想要搭建网站,如何挑选一台便宜合适的云主机?

京东科技开发者

云主机 网站搭建 测评 性能评测

Internet协议栈 TCP/IP模型 、以太网封装以及解封装过程、物理层、链路层、网络层、传输层、应用层的作用 OSI七层模型

Python-派大星

10月月更

JavaScript刷LeetCode模板技巧篇(一)

Geek_07a724

JavaScript LeetCode

完整议程 | 2022 XDR网络安全运营新理念峰会

未来智安XDR SEC

网络安全

认识VueCLI和Vite🔥

渔戈

前端 Vue3 10月月更

使用Python的requests库爬取网页表情包

何极光

Python 10月月更 爬虫案例

以太网数据链路层、Ethernet_II帧格式、IEEE802.3帧格式,以太网的MAC地址的组成,ARP地址解析协议的工作原理,单播帧、组播帧、广播帧的区别

Python-派大星

10月月更

深入浅出来谈谈webpack🔥

渔戈

前端 Vue3 10月月更

Flowable 服务任务执行的三种方式

江南一点雨

Java spring springboot flowable JavaEE

怎么购买公有云?具体流程是怎样?

行云管家

云计算 公有云 企业上云 云管理

编写高质量可维护的代码:组件的抽象与粒度_语言 & 开发_政采云前端团队_InfoQ精选文章