Skip to content

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

  1. 什么是数据流

=============

什么是流?

在数学中,一个用数学方式形式化了 “取决于时间的变化” 的一般想法。暂且定义流是响应时间变化的一个集合体。

什么是数据流

依据之前的定义,数据流即随时间变化的一个数据集合。

前端针对于现状 mvvm 模式下,数据即页面,在多数情况下,数据不变页面不变。那么我们转换一下,数据 ==> 页面,数据流是否可以等价为一个页面的变化集合。这个是什么,就是我们的业务逻辑。

当然,前面的假设,是经过很多转化,其实有很多漏洞和错误。但是可以作为一个简单的参考。

这个不是数据流的定义,只是作为一个引子思考。

1.png

  1. 现行前端数据管理模式 ==============

现行三大数据管理方式

  • 函数式、不可变、模式化。典型实现:Redux。

  • 响应式、依赖追踪。典型实现:Mobx。

  • 响应式,以流的形式实现。Rxjs、xstream。

Redux 模式(reduck)

redux 模式常规用法是作为整个应用全局状态管理使用。这只是作为一个提高跨组件通信的能力的工具。redux 的思想是作为独立于组件的一个数据仓库,对数据进行保护,保障数据稳定可靠。

可以简单理解 redux 是一个带保护的全局使用的 Context(useContext)。

针对于原生的提供了数据保护(dispatch+reducer),对于更改只允许使用 dispatch 进行更改。能够保障可回溯性,数据来源清晰,能够十分良好的隔绝副作用。

使用方面:

  • 优点:数据隔离,数据变化可溯源。

  • 缺点:多 redux 直接完全隔离,小型化困难,action 方法容易膨胀。

  • 业务方面使用:对于大型项目拆分设计概念不足,store 数据个人管控不友好,容易造成理解困难逻辑修改困难。

2.png

Mobx 模式

mobx 引入了全新的思想,将数据作为一个源头,拥有当数据变化时,通过计算状态,页面进行变化,并且根据 observable,自动根据依赖执行更新。虽然有了 action,但还是没有强制分离副作用。

mobx 就好像将数据和组件进行绑定,形成依赖关系,自动订阅和自动发布,状态变更组件就变更。将逻辑和视图直接绑定在一起,这本应该是十分高效的情况,但是因为深入了组件,副作用的处理还是不够清晰,对于个人把控还是不够友好。

流模式(rxjs)

rxjs 和 mobx 有些相似。rxjs 将所有的数据都可以随意的拆散和组合成一个新的节点,可以简单理解为将 redux 的 state 进行了打散成多个数据节点,每一个任意节点都可以进行类似 computed 的计算生成新的节点。

流模式相较于 redux 模式没有 action 的规范,却定义了更改的节点范围,只能更改定义的入口节点(一条流的起始节点)。rxjs 没有 mobx 从数据变化到页面变化这个功能,可以使用 useState 和 useEffect 实现或者现成的三方库 rxjs-hooks。

rxjs 的优势是,抽离所有数据源之后,剩余全部都是逻辑问题,副作用在抽离数据源的时候就已经剥离干净了(因为外部副作用数据也可以抽离成 rxjs 的节点),剩下就通过 api 和纯函数来编写具体的逻辑了。

又因为大量的 api,拆解 observable 节点的成本极低,所以逻辑拆分十分容易,可读性十分高。

rxjs 有推和拉的概念,在正常逻辑十分流畅的情况下,程序的代码应该是每个节点转变都会推动下一个节点的执行。在 rxjs 中将数据流进行串流好后,组件只要对于头部节点进行读写数据,对于尾部节点直接读取数据就可以,大部分逻辑全部被抽离出了组件。

使用方面:

  • 优点:逻辑拆分简便,纯函数式编程。

  • 缺点:数据拆分复杂,重设计,api 学习和理解成本高。

理想情况下,页面就是一个个无状态组件,行为改变数据。数据变化又触发逻辑变更,逻辑变更数据。数据又回流到页面,这是一个整体的闭环,以数据为核心,完美的做到数据驱动页面。

3.png4.png

新星 Recoil

recoil 是 facebook 官方推荐的一个状态管理库,作为一个 “新成员”,recoil 相比于之前的三种状态管理方式,做了很多取舍。它有节点的概念,有 atom(原子数据)和 selector(派生数据)但是不和 mobx 一样,recoil 是基于 Immutable(不变)模式。

recoil 的基础思想是 atom 数据之间没有关联,产生的关联数据全部由 selector 来产生,atom 的变动,相关的 selector 随之变动,这个和响应式流的思想一致的。

recoil 的优势,贴合 react,可以将 recoil 的实现当作通过 useMemo 包装的 context,api 使用可以满足只读,只写进行拆分,可以十分贴合最优渲染,降低无用的渲染。

上述前三种数据流没有什么优劣好坏之分,只是在不同场景中使用各有各的优势而已。

  1. 理想中的源数据编程 =============

数据与数据之间的关系

数据不是凭空产生的数据,数据可能又会产生新的数据。

数据之间推行的是最小可用原则,分而治之,这才更利于我们开发和维护。

个人把产生数据的起始数据定义为源数据。有些数据可以互相转换,那如何定义源数据???

定义源数据

从组件(页面)视角看一下数据。

  • 面向接口编程

大部分情况下前端和后端之间的数据交互就只有接口这一种。又因为真实的所有数据都是从服务端获取的数据,所以下意识的以服务端接口为数据起始。数据处理的链路较长,范围变大对于个人的理解要求是十分高的,个人认为这对于一个大型应用是不健康的。

  • 面向数据编程

分离接口请求,只关心组件状态,对于组件方面,任何数据不将其做区分,数据来就渲染。

个人将用户可交互的数据可以定义为源数据。因为前端接口请求也是由用户的信息请求来的。对于应用来说,只有用户的操作不可预知,其余操作都是可控的。

如果将可控的逻辑封装后抽离,管理的时候不需要再直接感知到这些,我们直面的就是用户的操作和页面的响应。

例如: 以单个列表页来说,用户选择的筛选项就是源数据,而接口请求回来的列表数据就是派生数据,由接口请求产生的页面 loading 态也可以是派生数据也可以是源数据。

5.png

redux 数据模型

在使用 redux 的时候没有很好的办法处理数据的层级关系,导致 store 中数据的池子越来越大,没有很强分层的概念,这也是 redux 小型化困难带来的,使用的时候会下意识将跨层级的数据存入 store。

其次 redux 没法很好的描述数据与数据之间的关系,有人说 computed 可以描述数据与数据之间的关联,简单意义上是没问题的,但是 computed 的局限性,跨 redux 无法支撑,如果要使用,必须将所有源数据汇集到同一个 redux 之间,这与最小可用原则是相违背的。

就是因为 redux 的设计模式不够灵活,导致会将大量数据与数据之间的转化逻辑积压在页面或者组件内部,这对于视图层是一种负担。

6.png

响应式流数据模型

为了方便理解,可以和之前一样将数据处理理解为 computed,每一个节点都可以随意衍生出一个新的节点,但是触发整条流的变化又只会在初始节点(源数据)节点,使用整条流的结果。

在流模式中对于组件或页面外层的数据没有任何层次之分,每个节点都是平级的,如果分层,可以在业务上分层,通过不断的拼接, 将业务逻辑进行串联,得出你想要的结果,相较于原先散乱在各地的逻辑,串联的流式逻辑在可读性上也更优。

流式数据的优点是拆分成本极低,这样也更符合我们的思想,代码块拆分,这样每一小块逻辑拆分出一个节点,逻辑复杂度就通过不断的拆解,变得十分低了,但是又不会因为拆解的过多,逻辑散乱。

  • 没有固化数据的层级,离散的数据,可以自由定义和拼接。

  • 以离散的数据,将逻辑串流,数据灵活之后,逻辑却是收敛,不会分散,做到了数据精准的使用。

7.png

reducx 和 rxjs 的数据管理范围

8.png

总结

综上所述,其实不同的模式带来的是针对不同场景的应用,redux 的快捷应用,快捷开发,数据变化的稳定,mobx 对于响应式的变化,都是各有各的特色。

在写代码的时候,我的感觉像是在构建一个动画的每一帧(视图),又要给出每一帧为什么变化(写逻辑,事件)的感觉,逻辑和视图混合在一起,对于整体的把控十分难处理,就像你需要对于整个动画的变化都掌握,抽离了视图和逻辑,逻辑只需要变化数据,视图只需要针对对应的数据变化而已。

参考文章

流动的数据——使用 RxJS 构造复杂单页应用的数据逻辑

精读《前端数据流哲学》

  • END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。