前端组件设计原则

一、值得注意的8个点

  • 层次结构和 UML 类图
  • 扁平化、面向数据的 state/props
  • 更加纯粹的 State 变化
  • 低耦合
  • 辅助代码分离
  • 提炼精华
  • 及时模块化
  • 集中/统一的状态管理

二、分点叙述

1、层次结构和类图

应用内的组件共同形成组件树, 而在设计过程中将组件树可视化展示可以帮助你全面了解应用程序的布局。一个比较好的展示这些的办法就是组件图。

UML 中有一个在 OOP 类设计中经常使用的类型,称为 UML 类图。类图中显示了类属性、方法、访问修饰符、类与其他类的关系等。通过图解辅助设计的方法值得参考。对于前端组件,该图表可以显示

  • State
  • Props
  • Methods
  • 与其他组件的关系( Relationship to other components )

因此,让我们看一下下面这个基础表组件的组件层次图,该组件的渲染对象是一个数组。


image.png

该组件的功能包括显示总行数、标题行和一些数据行,以及在单击其单元格标题格时对该列进行排序。在它的 props 中,它将传递列列表(具有属性名称和该属性的人类可读版本),然后传递数据数组。

这样会带来的一个比较重要的问题是它会需要你在开始 codeing 之前就需要考虑到具体细节的实现,例如每个组件需要什么类型的数据,需要实现哪些方法,所需的状态属性等等。

但事实上往往会出现一些预料之外的事情, 当然你肯定不希望因此去重构之前的某些部分,或者忍受初始设想中的缺点并因此扰乱你的代码思路。而这些类图的以下优点可以帮助你有效的规避以上问题,优点如下

  • 一个易于理解的组件组成和关联视图
  • 一个易于理解的应用程序 UI 层次结构的概述
  • 一个结构数据层次及其流动方式的视图
  • 一个组件功能职责的快照
  • 便于使用图表软件创建

2、扁平的,面向数据的 state/props

扁平 props 也可以很好地清除组件正在使用的数据值。如果你传给组件一个对象但是你并不能清楚的知道对象内部的属性值,所以找出实际需要的数据值是来自组件具体的属性值则是额外的工作。
state / props 还应该只包含组件渲染所需的数据。
(此外,对于数据繁重的应用程序,数据规范化可以带来巨大的好处,除了扁平化之外,你可能还需要考虑一些别的优化方法)。

3、更加纯粹的 State 变化

对 state 的更改通常应该响应某种事件,例如用户单击按钮或 API 的响应。此外它们不应该因为别的 state 的变化而做出响应,因为 state 之间这种关联可能会导致难以理解和维护的组件行为。state 变化应该没有副作用。
我们来看一个基本的 Vue 示例。我正在研究一个从 API 获取一些数据并将其呈现给表的组件,其中排序,过滤等功能都是后端完成的,因此前端需要做的就是 watch 所有搜索参数,并在其变化时触发 API 调用。其中一个需要 watch 的值是“zone”,这是一个过滤器。当更改时,我们想要使用过滤后的值重新获取服务端数据。watcher 如下:


image.png

你会发现一些奇怪的东西。如果他们超出了结果的第一页,我们重置页码然后结束?这似乎不对,如果它们不在第一页上,我们应该重置分页并触发 API 调用,对吧?为什么我们只在第 1 页上重新获取数据?实际上原因是这样,让我们来看下完整的 watch:


image.png

当分页改变时,应用首先会通过 pagination 的处理函数重新获取数据。因此,如果我们改变了分页,我们并不需要去关注数据更新这段逻辑。

让我们一下来考虑以下流程:如果当前页面超出了第 1 页并且更改了 zone,而这个变化会触发另一个状态(pagination)发生变化,进而触发 pagination 的观察者重新请求数据。这样并不是预料之中的行为,而且产生的代码也不够直观。

解决方案是改变页码这个行为的事件处理函数(不是观察者,用户更改页面的实际处理函数)应该更改页面值并触发 API 调用请求数据。这也将消除对观察者的需求。通过这样的设置,直接从其他地方改变分页状态也不会导致重新获取数据的副作用。

4、松耦合

组件的核心思想是它们是可复用的,为此要求它们必须具有功能性和完整性。

“耦合”是指实体彼此依赖的术语。

松散耦合的实体应该能够独立运行,而不依赖于其他模块。

就前端组件而言,耦合的主要部分是组件的功能依赖于其父级及其传递的 props 的多少,以及内部使用的子组件(当然还有引用的部分,如第三方模块或用户脚本)。

如果不是要设计需要服务于特定的一次性场景的组件,那么设计组件的最终目标是让它与父组件松散耦合,呈现更好的复用性,而不是受限于特定的上下文环境。

5、辅助代码分离

一个有效的原则就是将辅助代码分离出来放在特定的地方,这样你在处理组件时就不必考虑这些。以下列举一些方面:

  • 配置代码
  • 假数据
  • 大量非技术说明文档

因为在尝试处理组件的核心代码时,你不希望看到与技术无关的一些说明(因为会多滚动几下鼠标滚轮甚至打断思路)。在处理组件时,你希望它们尽可能通用且可重用。查看与组件当前上下文相关的特定信息可能会使得设计出来的组件不易与具体业务解耦。

6、提炼精华

一些无关紧要的东西,比如数据获取,数据整理或事件处理逻辑,理想情况下应该将通用的部分移入外部 js 或或者放在共同的祖先中。

单独从组件分的“视图”部分来看,即你看到的内容(html 和 样式)。其中的 Javascript 仅用于帮助渲染视图,可能还有一些针对特定组件的逻辑(例如在其他地方使用时)。除此之外的任何事情,例如 API 调用,数值的格式化(例如货币或时间)或跨组件复用的数据,都可以移动外部的 js 文件中。

让我们看一下 Vue 中的一个简单示例,使用嵌套列表组件。
先看下下面这个有问题的版本。
这是第一个层级


image.png

这是嵌套列表组件


image.png

在这里我们可以看到此列表的两个层级都具有外部依赖关系,最上层导引入外部 js 文件中的函数和 JSON 文件的数据,嵌套组件连接到 Vuex 存储并使用 axios 发送请求。它们还具有仅适用于当前场景的嵌入功能(最上层中源数据处理和嵌套列表的中度 click 时间的特定响应功能)。

虽然这里采用了一些很好的通用设计技术,例如将通用的 数据处理方法移动到外部脚本而不是直接将函数写死,但这样仍然不具备很高的复用性。如果我们是从 API 的响应中获取数据,但是这个数据跟我们期望的数据结构或者类型不同的时候要怎么办?或者我们期望单击嵌套项时有不同的行为?在遇到这些需求的场景下,这个组件无法被别的组件直接引用并根据实际需求改变自身的特性。

可以通过提升数据并将事件处理作为 props 传递来解决这个问题
这是改进后的第一级别


image.png

而新的第二级


image.png

使用这个新列表,我们可以获得想要的数据,并定义了嵌套列表的 onClick 处理函数,以便在父级中传入任何我们想要的操作,然后将它们作为 props 传递给顶级组件。这样,我们可以将导入和逻辑留给单个根组件,所以不需要为了能够在新的场景下使用去重新再实现一个类似组件。

7、及时模块化

我们在实际进行组件抽离工作的时候,需要考虑到不要过度的组件化
在决定是否将代码分开时,无论是 Javascript 逻辑还是抽离为新的组件,都需要考虑以下几点

  • 是否有足够的页面结构/逻辑来保证它?
  • 代码重复(或可能重复)?
  • 它会减少需要书写的模板吗?
  • 性能会收到影响吗?
  • 是否会在测试代码的所有部分时遇到问题?
  • 是否有一个明确的理由?
  • 这些好处是否超过了成本?

8、集中/统一的状态管理

许多大型应用程序使用 Redux 或 Vuex 等状态管理工具(或者具有类似 React 中的 Context API 状态共享设置)。这意味着他们从 store 获得 props 而不是通过父级传递。在考虑组件的可重用性时,你不仅要考虑直接的父级中传递而来的 props,还要考虑 从 store 中获取到的 props。

由于将组件挂接到 store(或上下文)很容易并且无论组件的层次结构位置如何都可以完成,因此很容易在 store 和 web 应用的组件之间快速创建大量紧密耦合(不关心组件所处的层级)。

原文链接:Front end component design principles
译文链接:[译] 前端组件设计原则
示例代码链接:github

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容

  • 作为一个合格的开发者,不要只满足于编写了可以运行的代码。而要了解代码背后的工作原理;不要只满足于自己的程序...
    六个周阅读 8,439评论 1 33
  • 3. JSX JSX是对JavaScript语言的一个扩展语法, 用于生产React“元素”,建议在描述UI的时候...
    pixels阅读 2,818评论 0 24
  • HTML模版 之后出现的React代码嵌套入模版中。 1. Hello world 这段代码将一个一级标题插入到指...
    ryanho84阅读 6,232评论 0 9
  • 深入JSX date:20170412笔记原文其实JSX是React.createElement(componen...
    gaoer1938阅读 8,061评论 2 35
  • 今天爸爸回来,西瓜一直黏着爸爸。我在找奥特曼服装,我挖这又挖那,最终还是把自己埋在了衣服堆里。我终于找到了奥特曼服...
    李宇_7792阅读 177评论 0 1