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

这是第 79 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注咱们吧~ 本文首发于政采云前端博客: 编写高质量可维护的代码:组件的抽象与粒度

前言

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

什么是组件

React 官方文档上说:组件,从概念上相似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展现内容的 React 元素。前端

Vue 官方文档说:组件是可复用的 Vue 实例,且带有一个名字。咱们能够在一个经过 new Vue 建立的 Vue 根实例中,把这个组件做为自定义元素来使用。vue

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

组件的抽象

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

为何要抽组件

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

其次,某些状况下,有一部分代码在不一样场景下实际上是能够复用的,例如新增和编辑的弹窗,可能只有弹窗的标题和某些字段有部分差别,此时不必把高度类似的代码复制两遍,增长代码的冗余。ide

图片

所以,在咱们平常开发中,组件抽取是有必要的,其目的在于代码的分层复用,下降项目的复杂度。函数

组件抽象的基本原则

单一性

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

复用性/通用性

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

此时有两种状况:

一种是不少不一样的项目间,可能存在相似的使用场景,所以会提炼出一个公共的组件,为了复用。通常咱们称之为基础组件或业务组件,姑且叫它公共组件吧。

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

公共组件和项目组件在设计上的侧重也有所不一样,公共组件要更多的考虑通用性,经过一个组件知足不一样项目中类似的使用场景,好比 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 中的各类组件一文中对 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 去获取,不过好像仍是“徒手传递”的人更多一点。但组件若是拆解的太粗略每每也会致使复用率低、难以维护等问题。

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

图片

  • 原子组件

若是说,原子是物质的基本组成部分,那么原子组件就能够做为构成咱们全部页面的最基本组成部分。原子组件,能够为上文中提到的基础 UI 组件,例如一个 Input 或一个 Button。它们每每具备不可再拆分的特性,是其余组件的基础。

  • 分子组件

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

图片

  • 生物组件

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

图片

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

图片

  • 模板组件

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

图片

  • 页面

最终,经过不一样模板组件的拼装,能够生成一个完整的页面。

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

总结

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

参考文献

React组件设计实践总结02 - 组件的组织

React 中的各类组件

React PureComponent 使用指南

组件化设计:原子设计实践

招贤纳士

政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在平常的业务对接以外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推进并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。

若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变既定的节奏,将会是“5 年工做时间 3 年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手推进一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给 ZooTeam@cai-inc.com