Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

长线h5前端架构设计 #62

Open
jiangjiu opened this issue Apr 13, 2023 · 0 comments
Open

长线h5前端架构设计 #62

jiangjiu opened this issue Apr 13, 2023 · 0 comments

Comments

@jiangjiu
Copy link
Owner

h5前端架构设计

  • h5前端架构设计
    • 为什么要做架构设计
      • 大事件为什么不做严格的架构分层
      • 长线活动完全不同
      • 破窗效应
    • 前端架构设计 ≠ 全局状态管理
      • 视图层框架不关心业务架构设计
      • ui状态和ui组件解耦
      • ui状态和外部数据的解耦
      • 听起来就是个MVC?
    • 基本原则
      • 分层
      • 单一职责/原子化
      • 高阶抽象、升维思考
      • 响应式编程
      • 约定大于配置,垂直场景强约束
      • 持续集成最佳实践+协作规范
      • 前端资产化
    • 具体实现
      • service层
      • store层
        • domainStore
        • actionStore
      • view 视图层
        • container 容器型组件
        • presentational 展示型组件
      • 异步控制流

为什么要做架构设计

架构终极目标,用最小的人力成本来满足构建和维护该系统的需求。架构始终是我们解决复杂度的一个工具,如果当前系统并不复杂,我们不需要为了所谓的优雅去过分改造与优化它,持续将成本置在一个较低水位,就是软件最好的解决办法。

大事件为什么不做严格的架构分层

在大事件活动的开发中,其实是没有严格意义的架构设计的,为什么在长线h5中要做呢?

上图体现了要不要架构设计在时间维度上的开发效率变化。

对于大事件活动来说:

  • 在线时间只有1~10天,业务流程、模块的简单划分就可以进入开发
  • 频繁的需求改动可以直接在之前基础上糊代码,简单灵活
  • 每一个业务流程只有一名同学参与,从头跟到尾,熟悉业务流程的所有细节,而且没有维护周期
  • 有更好的点子或者breaking change可以在下一次大事件中直接应用上线

这些属性决定了大事件没有维护的成本,简单业务不抽象复杂的架构设计反而可以带来超高的开发和交互效率,是非常契合业务形态的。

长线活动完全不同

而一个长线迭代的活动,情况就完全不一样了:

  • 参与开发和维护长线活动的同学相比一个大事件活动要多很多,而且会有频繁的人员调整
  • 不同的同学不能按业务流程隔离,会有多人协作同一个需求的场景
  • 会在之前的基础上进行开发,不熟悉之前的逻辑和流程
  • 需求一旦完成,需要持续在线上运行,很难有机会修改或重构

破窗效应

生活中,肯定有这样的经历:

1)炎热的夏天,你走在街上,买了一根雪糕。如果找不到垃圾桶,包装纸你会如何处理呢?是否找个有垃圾的角落,悄悄的扔掉?

2)超市买花生,你抓几个尝尝,花生壳一开始你会下意识的拽在手里。如果此时,看到地上有一堆的花生壳,你就会将自己磕的花生壳也扔在地上。

……

于是,同等效应。路上随处可见的垃圾,广告墙上乱七八糟的涂鸦……我们离优雅文明的环境就这样渐行渐远。

环境早就脏了,我扔的这点儿垃圾根本起不到关键性作用”、“反正也不是我先这么做的”,每个人都抱着这样的想法自我暗示。不知不觉,我们就变成让环境恶化的第二双手、第三双手……

这是一种有规律可循的心理现象,在心理学上称为“破窗效应”。指的是一件事情、一个东西一旦稍微被破坏了,人们就会有意无意地任其变得更坏。

随着活动时间的增长,如果没有良好的分层和隔离,那耦合部分会越来越多,越来越重,后续的需求迭代效率也会大大降低,还要花大量的时间回归业务逻辑和寻找bug。

前端架构设计 ≠ 全局状态管理

一定会有同学有这个疑问,是不是引入了全局状态管理库redux/pinia,就认为是已经做好了分层?

答案是否定的,架构分层是一种设计思想,redux/pinia只是某一层的具体实现;分层也不止全局状态这一层,根据业务可以扩展出更多的层,因为这里说的前端架构设计本质上是针对业务需求实现最小人力的开发和维护,是从业务角度出发的。

视图层框架不关心业务架构设计

不管是react还是vue,就像他们文档声明的那样,本质上他们都是视图层的编程框架,只不过大家实现思路有差异,解决的问题其实是一致的。

其核心思想是 UI = f(State) 。

视图层只会关心如何处理状态驱动视图的最佳实践,但并不会考虑业务逻辑和业务状态管理。

没有状态管理的时候,业务状态是分散在各个组件内部的,这也意味着业务状态和ui组件产生了严重的耦合;简单业务这确实没什么问题,只要复杂度稍微上升或者有所迭代,频繁的组件改动带来了高成本的维护性,可复用性成为了奢望,多人协作无法并行。

在用增的绝大部分场景下,开发者要解决的并不是UI = f(State),用UI = reactive/observable(State)描述也许更合适。

这里的reactive/observable实际上就是ui层的业务逻辑,比如弹窗队列、动画、交互逻辑、异步请求等等。在视图层处理所有的交互业务逻辑和状态,显然是不合理的。

ui状态和ui组件解耦

全局状态管理在这时候的优势就体现出来了,ui组件可以只专注于视图层的业务渲染,展现什么和何时展现被提升到全局状态管理中,也就是vuex、pinia、redux这样的状态管理库中。

这样做的好处多多:

  1. 跨组件通信变的简单
  2. 状态唯一,修改来源统一
  3. 业务逻辑不关心ui组件实现,ui状态和组件解耦,这两部分的可复用性、可维护性大大提高
  4. 业务状态的抽象+业务逻辑的实现和ui组件可以并行多人开发
  5. 解耦后,业务逻辑可组合,可以实现切面编程和异步控制流编程,这是业务层的强需求(比如打点、交互队列),并不是ui组件的强需求
  6. 解耦后,一个需求的逻辑大大简化,不需要面向过程编程,可测试性提高

听起来确实不错,但实现起来仍然有较多问题,比如

  • 如何划分全局ui状态和组件内部状态?
  • ui组件如何响应全局状态交互?
  • ui状态不止面向当前切面,还会有异步交互控制流的需求,除了面向某一时刻的UI层xy轴开发业务逻辑,如何响应时间Z轴的需求?

后面的具体实现再来解决这些问题。

ui状态和外部数据的解耦

前端的状态不是无根之水,一般业务场景下,初始状态都是由后端下发,页面生命周期中还会有异步接口请求更新,请求本身可能涉及到重试和缓存,localStorage的存取,端能力获取的某些状态,大量的用户交互带来的状态的改变

  • 某些ui状态会频繁变动,但请求到的数据源并不会;
  • 数据源会涉及到缓存重试持久化这些问题,而ui状态大部分都是实时生成,实时响应的

这意味着:

  1. ui状态部分派生自领域数据,比如用户状态、列表数据,可以直接用来渲染view
  2. 部分ui状态是根据领域数据映射出来的,叠加着业务逻辑,最典型的就是弹窗队列(后端下发展示5个弹窗,业务逻辑是一个一个出现,且过程中需要响应用户操作进入下一个状态,极端场景下还会产生弹窗插队、交互重排序等需求)
  3. 派生的ui状态不关心数据层是如何获取的,数据层的逻辑也可以和ui层的业务逻辑解耦了

听起来就是个MVC?

思想是一致的,但实现上和后端传统的MVC架构区别非常大。

目前用增的场景,领域数据都是从后端获取的,不存在实质意义上的model层,也无需花大力气维护M层。

ui状态层也并不是对等controller层,这一层是存在状态的,而且提供了业务逻辑的实现以及响应用户交互的真正实现,这和传统M层倒是有点类似。
传统M层也就是领域模型并不关心view层,但ui状态层本身就是为了ui展示衍生出来的,这其实反应了前后端真正处理的业务是有极大差异的。

至于view层,MVC中的v层只存在展示功能,一般没有业务上的逻辑,但现代前端的V层显然比传统产品要复杂的多的多。
不但实现了组件状态和dom的自动绑定和更新,用增场景下的动画、ssr hydration这些都主要在v层实现,他们其实已经是业务逻辑的一部分,而且是和view层深度绑定的,无法彻底剥离。

说了这么多,其实传统的后端MVC真正处理的业务集中在M层和C层,现代前端产品的业务重心集中在V层以及衍生的ui状态层,业务重心不一致自然无法直接套用一样的架构设计。

基本原则

分层

业务复杂度隔离,避免耦合、复杂度外溢

单一职责/原子化

限制模块的职责范围,不需要关心上中下游模块的实现

高阶抽象、升维思考

一个业务需求往往不一定是完全扁平化的,很有可能在多个维度中跳跃或并行。跳出当前的维度,或在更高阶进行抽象,反而可以让低维需求保持原子化。

响应式编程

响应式编程是一种面向数据流和变化传播的编程范式。 这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

vue3框架本身就是一种响应式的数据绑定和视图更新,但我们对响应式编程的使用不止于view层。

约定大于配置,垂直场景强约束

约定即强约束,降低自由度,开发效率变高和关注点集中
配置极简化,可变的范围非常小,需要操心的配置也足够少

持续集成最佳实践+协作规范

比如现在的css开发还处于裸写less的阶段,实际上和UE、UX一起共建原子化样式规范+业务场景样式规范+交互规范之后,可以极大的降低各方的开发成本,也可以更好的约束不合理的行为。

前端资产化

强约束+分层+规范,很容易形成高质量的稳定输出,进一步形成真正的前端资产沉淀。

有了资产沉淀,可以顺势推出垂直向的低代码平台或相关服务,甚至将开发流程直接搬运到低代码平台构建的服务流程中。

具体实现

image

service层

处理外部数据的输入与输出,包含同步注入数据、异步接口请求、端能力获取数据和状态、存取localStorage等等。

作为一个抽象层屏蔽掉内部实现以及相应的缓存、重试、更新等逻辑。

也可以更好的集中管理外部输入与输出。

store层

我们使用pinia来实现全局状态管理,这是整个应用的核心层。

全局状态管理中的状态,绝大部分都来源于或派生自service层的数据获取,同时需要响应用户操作以及页面生命周期的事件。

store根据职责划分为两种:

domainStore

domainStore存放的是单纯的数据、App及用户状态,可以直接用来渲染和判断的,比如用户名称、当前积分、列表数据以及用户是否登录、页面是否可见等。

后端维护的领域数据和这里的domainStore应该是对应的
通常这里的数据不会频繁更新,即使有状态更新也是自维护的,不需要响应用户交互事件。

如果有需要,可以通过流式的方式来订阅更新service层的数据。

actionStore

actionStore存放的通常是纯粹的前端业务逻辑或响应用户的交互状态,最常见的就是弹窗的异步交互队列,任务列表上某个任务产生的弹窗的状态控制。

通常情况下,actionStore可能使用domainStore的响应式数据。

view 视图层

组件根据职责划分为两类:

container 容器型组件

容器型组件是与store直连的组件,为展示型组件或其它容器组件提供数据和行为,尽量避免在其中做一些界面渲染相关的事情。

需要注意的是,ui状态和容器组件并不需要一一对应,这取决于业务需求,多个容器组件依赖同一份或者同多份ui状态是可能的;某一容器下,ui组件可能是嵌套的组件树,但store中存放的状态可能是扁平的。

presentational 展示型组件

展示型组件独立于应用的其它部分内容,不关心数据的加载和变更,保持职责单一,仅做视图呈现和最基本交互行为,通过props接收数据和emits回调函数输出结果,保证接收的数据为组件数据依赖的最小集。

一个有成百上千展示型组件的复杂系统,如果展示型组件粒度切分能很好的遵循高内聚低耦合和职责单一原则的话,可以沉淀出很多可复用的通用业务组件。

异步控制流

上面的分层设计主要针对的是简单场景下的数据获取与状态响应。

image

复杂的交互、请求场景下,页面存在时间轴(Z轴)维度的状态控制。

Z轴维度下,上面的三层都可能存在异步流:

  • 数据service层可能存在并发请求的合并和竞态处理;
  • store层会有弹窗交互队列;
  • view层可能存在数字滚动动画、响应用户交互等场景。

所以,对于异步控制流的处理并不局限在某一层,可以使用工具库简化这些业务场景。

RxJS 是 Reactive Extensions for JavaScript 的缩写,起源于 Reactive Extensions,是一个基于可观测数据流 Stream 结合观察者模式和迭代器模式的一种异步编程的应用库。RxJS 是 Reactive Extensions 在 JavaScript 上的实现。

强烈推荐阅读rxjs文档和示例,使用rxjs简化复杂异步流的开发,可以让这个维度的需求实现非常简单清晰。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant