本周精读文章：[单页应用的数据流方案探索](https://zhuanlan.zhihu.com/p/26426054)

# 1 引言

<img src="https://img.alicdn.com/imgextra/i1/O1CN01FRCFcw1SsooXOTIW2_!!6000000002303-2-tps-640-330.png" alt="logo" width="500" />

> 前几期精读了前端模块化、语法相关的文章，这次讨论另一个举足轻重的话题：数据流。
> 数据流在前端的地位与工程化、可视化、组件化是一样重要的，没有好的数据流框架与思想的指导，业务代码长期肯定倾向于不可维护的状态，当项目不断增加功能后，管理数据变得更加重要。

早期前端是没有数据流概念的，因为前端非常薄，每个页面只要展示请求数据，不需要数据流管理。

随着前端越来越复杂，框架越来越内聚，数据流方案由分到合，由合又到了分，如今数据流逐渐从框架中解绑，形成了一套通用体系，供各个框架使用。

虽然数据流框架很多，但基本上可以分为 `双向数据流党`、`单向数据流党`、`响应式数据流党`，分别以 `Mobx`、`Redux`、`Rxjs` 为代表呈现三国鼎立之状，顺带一提，对 `css` 而言也有 `css in js` 和纯 `css党` 势均力敌，前端真是不让人省心啊。这次我们来看看民工叔徐飞在 **QConf** 分享的主题：**单页应用的数据流方案探索**。

# 2 内容概要

文中主要介绍了响应式编程理念，提到的观点，主要有：

1. `Reactive` 数据封装
2.  数据源，数据变更的归一
3.  局部与全局状态的归一
4.  分形思想
5.  action 分散执行
5.  app 级别数据处理，推荐前端 `Orm`

整体来看，核心思路是推荐组件内部完成数据流的处理，不用关心使用了 `Redux` `Mobx` 或者 `Rxjs`，也不用关心这些库是否有全局管理的野心，如果全局管理那就挂载到全局，但组件内部还是局部管理。

最后谈到了 `Rxjs`、`xstream` 响应式数据流的优势，但并未放出框架，仅仅指点了思想，让一些读者心里痒痒。但现在太多”技术大牛“把”业界会议“当成了打广告，或者工作汇报的机会，所谓授人以鱼不如授人以渔，这篇文章卓尔不群。

# 3 精读

一切技术都要看业务场景，民工叔的 **单页应用数据流方案** 解决的是重前端的复杂业务场景，虽然现在前端几乎全部单页化，但单页也不能代表业务数据流是复杂的，比如偏数据展示型的中台单页应用就不适合使用这套方案。

此文讨论的是纯数据流方案，与 `Dom` 结合的方案可以参考 [cyclejs](https://cycle.js.org/)，但这个库主要搭建了 `Reactive` -> `Dom` 的桥梁，使用起来还要参考此文的思路。

## 3.1 响应式数据流是最好的方案吗？

我认为前端数据流方案迭代至今，并不存在比如：面向对象 -> 函数式 -> 响应式，这种进化链路，不同业务场景下都有各自优势。

#### 面向对象

以 Mobx 为代表，轻前端用的较多，因为复杂度集中在后端，前端做好数据展示即可，那么直接拥抱 js 这种基于对象的语言，结合原生 `Map` `Proxy` `Reflect` 将副作用进行到底，开发速度快得飞起。

数据存储方式按照视图形态来，因为视图之间几乎毫无关联，而且特别是数据产品，后端数据量巨大，把数据处理过程搬到前端是不可能的（为了推导出一个视图形态数据，需要动辄几 GB 的原始数据运算，存储和性能都不适合在前端做）。

#### 函数式

以 Haskell 为代表，金融行业用的比较多，可能原因是金融对数据正确性非常敏感，不仅函数式适合分布式计算，更重要的是**无副作用**让数据计算更安全可靠。

个人认为最重要的原因是，**金融行业本来很少有副作用**，像前端天天与 `Dom` 打交道的，副作用完全逃不了。

#### 响应式

以 Rxjs 为代表，重前端更适合使用。对于 `React native` 等 App 级别的开发，考虑到数据一致性（比如修改昵称后回退到文章详情，需同步作者修改后的昵称），优先考虑原始类型存储，更适合抽象出前端 `Orm` 作为数据源。

其实 `Orm` 作为数据源，面向对象也很适合，但响应式编程的高层次抽象，使其对数据源、数据变动的依赖可插拔，中等规模使用大对象作为数据源，App 级别使用 `Orm` 作为数据源，因地制宜。

## 3.2 分形思想

分形思想即充血组件的升级版，特点是同时支持贫血组件的被外部控制能力。

### 分形的优点

分形保证了两点：

1. 组件和数据流融为整体，与外部数据流**隔离**，甚至将数据处理也融合在数据管道中，便于调试。
2. 便于组件复用，因为数据流作为组件的一部分。

如果结合文中的 **本地状态** 概念，局部数据也放在全局，就出现了第三点好处：

3. 创建局部数据等于创建了全局数据，这样代码调试可局部，可整体，更加灵活。

**本地状态** 可以参考 dva 框架的设计，如果没有全局 `Redux` 就创建一个，否则就挂载到全局 `Redux` 上。

### 分形的缺点

对于聊天室或者在线 IDE 等，全局数据居多，很多交叉绑定的情况，就不适合分形思想，反而纯 Redux 思想更合适。

## 3.3 数据形态，是原始数据还是视图数据？

我认为这也是分业务场景，文章提到不应该太偏向视图结构数据，是有道理的，意思是说，在适合原始结构数据时，就不要倾向于视图结构数据了。但有必要补充一下，在后端做了大量工作的中台场景，前端数据层非常薄，同时拿到的数据也是后端服务集群计算后的离线数据，显然原始数据结构不可能放在前端，这时候就不要使用原始数据存储了。

## 3.4 从原始数据到视图数据的处理过程放在哪

文中推荐放在 `View` 中处理，因为考虑到不想增加额外的 `Store`，但不知道这个 `Store` 是否包含组件局部的 `Store`。业务组件推荐使用内部数据流操作，但最终还是会将视图数据存在全局 `Store` 中，只是对组件而言，是局部的，对项目而言是全局的，而且这样对特定的情况，比如其他组件复用数据变更的监听可以支持到。

# 总结

我们到头来还是没有提供一个完美的解决方案，但提供了一个完整的思路，即在不同场景下，如何选择最合适的数据流方案。

最后，不要盲目选型，就像上面提到的，这套方案对复杂场景非常棒，但也许你的业务完全不适合。不要纠结于文中为何没有给出系统化解决方案的 Coding 库，我们需要了解响应式数据流的优势，同时要看清自己的业务场景，打造一套合适的数据流方案。

最后的最后，如有不错的数据流方案，解决了特定场景的痛点，欢迎留言。

**如果你想参与讨论，请[点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，每周五发布。**