重新思考 React 项目架构

在开始聊之前,我想定义一下架构的概念。大部分小伙伴都会问一个问题:

前端能有啥架构,不就是文件放到哪个文件夹吗?

对,但也不对,所以我们需要对齐认知:

到底什么是架构?

在开始之前,我希望你将视野从前端这个点扩展开,站在你正在参与开发的整个系统来思考这个问题。

然后我们先来看看一个相对权威的对架构的定义是什么样的:

Architecture is the fundamental organization of a system embodied in its components, their relationships to each other, and to the environment, and the principles guiding its design and evolution.

这是 IEEE1471 对于架构的一个定义,它来自于 IEEE标准协会 下辖的 IEEE计算机协会

从这里的定义来看架构指的是“系统的基本组织”,体现为“组成这个系统的组件以及组件之间的关系”,同时还应该包含该系统“与环境的关系”,最后架构还应该“指导系统的设计和演变”。

好的,那么系统又是啥?

A system is a collection of components organized to accomplish a specific function or set of functions. The term system encompasses individual applications, systems in the traditional sense, subsystems, systems of systems, product lines, product families, whole enterprises, and other aggregations of interest. A system exists to fulfill one or more missions in its environment.

所以系统指的是“为了完成某个或者一系列功能而组合起来的组件的集合”,它可以是“单个应用、传统意义上的系统、子系统、系统的系统、产品线、产品系列”等。

这里的组件可以理解成系统中的组成部分,比如一个系统中的数据库、一个个拆分好的服务、不同的前端应用等。

组件之间的关系指的是组件之间的交互关系是怎样的,比如前端应用通过 http 请求调用后端服务,后端服务调用数据库完成数据更新。

我们好像还漏了个环境

The environment, or context, determines the setting and circumstances of developmental, operational, political, and other influences upon that system.

可以看到这里的环境并不单纯指开发人员平时所说的“测试环境、生产环境”。它还包含了运营环境、政治环境等会对系统造成影响的更为广义的环境。

总结来看,架构像是一种蓝图,它规定了系统中都有什么样的组件,组件与组件之间如何交互,当变化出现时应该如何进行扩展和改变来进行应对,从而实现对系统的演进。更重要的是它还需要关注组织结构、业务特点和部署环境等环境限制,在受限的情况下进行合理规划。

那么前端架构呢?

到这里我们就需要将目光从整个系统再放回到前端这个应用上来。

由于各个项目各个组织的环境不尽相同,没有办法找到一个途径来解决不同环境下的不同问题,所以这篇文章也不会关注系统的环境,会将重点放在架构描述的前半部分。

接下来我会以 React 为例进行展开,首先是 ng 已经做的太完善了,没必要在官方文档的基础上造轮子,照着文档来就完事了。其次是 Vue 我没咋用过,没啥发言权,最后是 React 以“自由”著称,各种写法五花八门,我想总结一个相对靠谱的样子出来。

组件

在开始聊组件前我想再次和大家对齐认知 - React 只是一个 UI 库。

A JavaScript library for building user interfaces.

你说它简陋也好,说它扯淡也行;你夸它自由也罢,扁它是撒手掌柜也可以。但这就是事实,它真的就只管用它的规则来渲染 UI。它不管你的路由,不管你的 API,甚至不太想管跨页面(全局) UI state。

那都是你开发者的事,关我 React 什么事?

于是“理所应当”,React 的生态非常“繁荣”。为了能更好地完成你的项目,你需要补齐一些组件:

Request Client

这是你与后端服务通信的关键,大多数项目在 fetch API 普及后就开始使用 fetch API 了,当然也有很多人偏向于老牌的 axios,选什么不重要,关键你得有。

更多人开始慢慢地在 Client 外面包一层,我把它叫做 Client Wrapper 它可以帮你更好地管理缓存,提升开发体验,比如 React QuerySWR

View Model

我其实没有想到更好的名字来命名这个组件,这个组件百分之九十九的情况下是一个 React Hook,它向外暴露 React 组件所需要的所有 callback 和 data,负责处理和持有 UI 所需的 data 和相关计算,同时提供用于反馈用户操作的 callback。

奇怪的点在于这个命名来自于面向对象,但是由于 React 开始全面拥抱函数式,一个函数被用 Model 来命名着实有点奇怪。

Model

这个组件主要用来处理业务逻辑和后端数据转换,相当于对业务的建模,是一个充血模型。它一般被 View Model 调用,用来转换后端数据、内聚相关业务逻辑等等。

它带来的好处是屏蔽后端业务对前端 UI 的影响,只要 Model 保持接口不变,后端数据变化或者业务发生改变,在前端UI设计不变的情况下可以将变化屏蔽在 Model 内,不用对其它地方进行修改。

它还有可能会处理一些通用的逻辑,比如处理缓存,比如错误处理等等,所以也可能会出现类似于 Error Model 这样的 Model。

与 View Model 这个东西的命名同理,把一个函数叫做 Model 确实有些奇怪。但是数据加行为这样的组合好像叫 Model 也没啥不妥?

组件关系

在拥有这些组件的情况下来看看我们这个系统的架构会长成什么样子。

architecture-of-react

图中箭头代表调用关系,连线代表可能成为这个组件的一些实现。Client Wrapper 画成了虚线是因为可能有的项目觉得它是多余的,并不需要,所以是一个可有可无的组件。

当用户操作页面上的元素时,React Component 充当的 View 组件会触发 View Model 中暴露的 callback,callback 会根据具体情况调用 Client 和(或)Model 处理响应逻辑,最后根据不同的情况更新 View Model 中的 data 实现对页面数据的更新。

需要说明的是图中省略了一些技术组件,比如全局 state 管理,比如可能涉及的事件处理组件等等。他们应该按照职责的不同被归属到不同的组件中,而如果你发现有一些组件没办法进行归属那或许就是你扩展自己架构的时候了。

目录结构

到这里我们已经聊了组件和一半组件之间的关系。为什么是一半呢?因为组件之间不仅仅有调用关系,它还应该包含在文章开头提到的:

文件该放到哪个文件夹里

组件应该如何在项目中被归类的问题我在这篇文章中已经聊过一次了,但是既然是“重新思考”架构,理所应当我想对里面的东西做一些改进。

whole-folder-of-project

目录划分的思想没有发生改变,还是分类分层,但是有了不一样的架构之后会有一些改变。

首先是一些“无关紧要”的目录,比如 assets mocks request context 它们的作用和文件夹一样已经很表意了,不做过多解释。

components

与上一篇文章不一样的是,我将 ui-components 和 components 合并到了一起。

在实践的过程中发现,一些时候 ui-components 和 components 没有明显的界限,比如里面稍微有一些逻辑但又不多的时候放在哪里就会很尴尬;再比如团队里大家对于不同组件的认知不一样,分类的时候就会不一样;

最后其实这样拆分开以后并没有多太多的好处,如果仅仅是为了类似于“拆一个公共的 UI 库”会很方便这样的理由维护两个 components 文件夹会增加许多不必要的心理负担,要考虑的问题会城北增长,属实没有必要。

其次合并后的文件夹也可以减少维护成本,降低管理负担。

model

由于 Model 这个组件在架构中的出现,项目中应该不会再出现各种各样的 utils 函数了,他们应该被以各种模型的方式内聚到一个个 Model 中。如果某个 Model 变得很复杂,说明你的模型出了些问题,可以考虑对其进行拆分下放到一个文件夹中来管理。

pages

这个文件夹没有太多改变,但是由于 Model 和 View Model 的引入会有些许不同:


pages-folder

最后

我们从架构的定义开始聊起一直到聊完前端架构中组件与组件之间的关系。有意思的是最后我们的架构变成了一个几乎等于 MVVM 的架构,于是我第一次在我脑子里体会到了架构的演进。

在我四年的一个个项目中,项目架构慢慢清晰起来:

Class 时代按部就班拆分文件夹
Functional 时代按部就班拆分文件夹
为了做 TDD 将逻辑与 UI 以 hooks 的方式分离
为了解决杂乱的 utils 文件和隔离业务逻辑加入了 Model

最后我查阅了一些关于 Android 项目 MVVM 的资料,发现里面存在大量模版代码,也回想起项目上 Android 小伙伴吐槽他们所使用的整洁架构和 MVVM 存在大量无用的中间层。

所以个人认为至少在现在我遇到的这些项目中这些模版代码属实没有必要,这些组件已经能够支撑起大多数业务项目。

另一方面我也真真切切体会到了思考和多元化的力量。这些演进出现的契机要么是为了解决难题(比如大家说的前端不好 TDD);要么是我在写了一段时间后端代码同时学习 DDD 的东西加入进来的(建模);要么是 pair 或看书看博客的时候一些间接直接的输入(和组里 TL pair 讨论,看到邱大师的博客)。

可能它现在不是最后的终点,但它是我现在能看到的前方。

References

  1. GUI Architectures
  2. How To Structure React Projects From Beginner To Advanced
  3. Tao of React - Software Design, Architecture & Best Practices
  4. 可维护的 React

本文由mdnice多平台发布

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

推荐阅读更多精彩内容