前端工程整洁架构实践
Posted Songlcy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端工程整洁架构实践相关的知识,希望对你有一定的参考价值。
背景
Genebox App 项目从2019年初开始启动,经历1.+、2.+、3.+版本,目前已形成较为稳定的功能架构体系。项目开发框架以 Redux 为主,在使用 React Native 实现众多业务的过程中,经历了前期探索,中期大量应用,以及正在进行的后期架构和性能优化三个阶段,在经历了多个版本迭代后,一些前期未考虑到的问题渐渐浮现。我们重新审视和思考一些前期实践项目的整体优化方向。基于 Clean Architecture 整洁架构之道的思想,技术大重构的期望随之出现。
架构模式
架构模式,一般分为两类:MV* 和 Unidirectional
- MVC
- MVP
- MVVM
- DataBinding
- 无状态
- Redux
- Mobx
最初的 MVC 将模块划分为展示界面的 View,数据模型 Model 和负责处理二者关系的 Controller 。从 MVC 到 MVP 的过程将 Model 和 View 完全隔离。随着 Databinding 技术的引入,MVP 进化到了 MVVM,使得 View 完全无状态化。
Unidirectional 系列相较于 MV*,则采用了消息队列式的数据流驱动的架构,其中具有代表性的 Redux 采用了统一的状态管理,带来了状态的有序性和可回溯性。
MV* 系列在 ios、 android 生态圈中已得到成熟广泛的应用,而在 React 技术栈的 Web 前端领域, Redux 是最主流的数据管理方案。
不同平台选择不同,这其中有框架 API 设计的原因,有编程语言的原因,以及面对的业务逻辑复杂度不同。React Native 是 React 和 Native 的混合体,原有的 Native 框架 API 被映射成 React Component 生命周期,编程语言也发生了变化,不变的是业务场景和逻辑复杂度。
Redux 是大型 RN 项目的标配,不过实践结果表明, Redux 的一些固有设计并不能很好的应对复杂的应用场景。因此,选择相较于MV*系列,又对Presenter/Controller做了进一步拆分的Clean Architecture最为合适当前应用开发场景。
Clean Architecture
Clean Architecture 是 Robert C. Martin (Uncle Bob) 在2012年提出的用于构建可扩展、可测试软件系统的概要原则。这些架构产生的系统特点是:
- 框架无关性 - 框架只是一个工具,系统不与框架绑定
- 可被测试 - 业务逻辑与UI、数据库等隔离,方便单元测试
- UI 无关性 - 不需要修改系统的其它部分,就可以变更 UI,如将 React 替换为Vue
- 数据库无关性 - 业务逻辑与数据库之间需要进行解耦
- 外部机构(agency)无关性 - 系统的业务逻辑,不需要知道其它外部接口,诸如安全、调度、代理等
(图片来自 Clean Coder Blog https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
上图可以看出,整洁架构从外到内,分为四层:
- Frameworks & Drivers - 由框架和工具组成,比如各种前端框架,数据库访问工具等。
- Interface Adapters - 作用是转换数据,连接内层与外层
- Application Business Rules - 将多个业务实体封装为高级具体的业务用例
- Enterprise Business Rules - 单个业务实体,可以是具有方法的对象,也可以是一组数据结构和函数
不同层代表软件系统中不同领域,外层是机制(mechanisms),内层是策略(policies)。
层与层之间遵循一个依赖关系原则:外层指向内层,机制指向策略。内层中的任何东西都不能知道外层中的某些东西。特别是外层中声明的内容的名称不得被内层中的代码提及,包括功能、类、变量或任何其他命名的软件实体。出于同样的原因,外层中使用的数据格式不应该被内层使用,特别是当这些格式是由外层中的框架生成时。外圈中的任何东西不应该影响内圈。
模块结构
模块内部遵循Clean Architecture原则,分为四层:
- ViewModel & StatelessView - React框架相关代码,只负责界面展示,样式,动画和传递交互事件
- Presenter - 连接 ViewModel 和 Interactor,连接模块内部和外部,不存在业务逻辑
- Interactor - 持有多个Model,将它们封装成高级的业务逻辑,供 Presenter 调用
- Model - 独立的业务逻辑实体,提供方法给 Interactor 调用
代码实现
TypeScript技术栈能够可靠地支撑大型复杂项目工程。TS 补齐了 javascript 在数据类型方面的短板,这对大型项目的持续维护和稳定交付非常重要。
TS类型系统描述了数据结构、function的入参和返回值的类型和 class 对外暴露的方法,面向接口编程变得可能,我们编码时不再通过阅读代码了解上下文,而是面向接口实现逻辑。同时,IDE的支持带来了方便的代码智能提示和跳转,提升了开发效率。
在TS支持下,一个规则的结构如下所示:
- ModuleBuilder.tsx
- StatelessView.tsx
- IViewModel.ts (interface接口)
- ViewModel.ts(IViewModel接口的具体实现)
- IPresenter(interface接口)
- Presenter(IPresenter接口的具体实现)
- IInteractor(interface接口)
- Interactor(IInteractor接口的具体实现)
- Model(OOP实体类)
Builder.tsx
持有父组件通过 props 传入的模块初始化参数,在生成各层实例时传入对应的构造函数
ViewModel.tsx
viewModel层具体实现, 持有类型为 IPresenter 的 presenter 实例和多个无状态子组件。UI交互的响应指向 presenter 暴露的方法,使用 state 持有界面数据,并以 props 的方式下发给无状态子组件StatelessView。
StatelessView.tsx
没有业务逻辑,没有state,单纯展示 viewModel 下发的props。
IPresenter.ts (Interface)
presenter层契约,描述暴露给 viewModel 层的方法,通常为响应 UI 交互逻辑。
Presenter
presenter层具体实现,以接口的形式持有 viewModel 和 interactor 对象,关联业务逻辑和界面展示逻辑。持有 eventBus 和 apiBus 对象,用于模块间通信。拥有 onViewModelAttach 和 onViewModelDestroy 生命周期,对应 viewModel 的创建和销毁。
IInteractor.ts (Interface)
interactor层契约,描述暴露给 presenter 层的方法,这些方法表示具体的业务逻辑。
Interactor.ts
interactor层具体实现,持有多个 model 对象,将它们封装为高级的业务用例供 presenter 调用。当只有一个 model 时,interactor可以不存在,而用唯一的 model 替代。
Model.ts
相对独立、内聚的业务实体,暴露方法供 interactor 调用。
React Component 的不足
在 Thinking in react 中有说到:模块由多个 Component 组成,state放置在负责展示他们的 Component 中。当业务场景变得复杂后,会出现以下问题:
- 在组件之间复用状态逻辑变得困难 - Component的层次结构,对布局和界面展示友好,对业务逻辑不友好。业务上不相关的 state 组合在一个Component中,破坏业务逻辑的内聚,导致业务代码难以测试、复用和维护。
- 混乱的componentWillReceiveProps - React的数据流自上而下,当业务逻辑同时依赖 props 和state时,必须在 componentWillReceiveProps 中判断是否对应的props被改变。
React Hooks 的不足
为了复用组件间状态逻辑,可以将逻辑封装为一个 Hook,供其他组件使用。
为了 Class Component 的生命周期方法不被不相关的状态逻辑和副作用充斥,则换做在 Function Component 重复使用 Effect Hook ,将这些逻辑进行分类。
同时,相较于在 Class 要写类似 bind 的代码,Function Component可以少写很多代码。
同样,当业务场景变得越来越复杂,会出现以下问题:
- React只是构建用户界面的框架。
- 组件树的结构利于描述布局逻辑,但对于业务逻辑不够友好。
- 在完成从 Native 迁移 React Native 技术栈之后,后续如果需要移植到小程序或 Flutter 如何成本最低?
Hook 并不能很好的解决这些问题,而Clean Architecture则是参考答案。如果说 Hook 的出现,是为了让开发者更方便地把 state 放入 Component ,那么 Clean Architecture 则是让开发者不要把 state 放入 Component 中。
Redux 的不足
- 单一数据源(Store)变大后维护困难
- 架构层次代码复杂度与业务复杂度成正比
- 所有组件都依赖集中的单例 Store ,当需要将组件改造成为一个独立模块,复用于其他项目时,修改工作量较大
以上是关于前端工程整洁架构实践的主要内容,如果未能解决你的问题,请参考以下文章