Reactive programming
这周不放文章了,把我今天在 team 的讲座拿出来和大家分享。
今天讲的是 reactive programming —— 更确切地说,是 spreadsheet-like programming。说 reactive 容易和 FRP(functional reactive programming)混淆,而今日的主角 hoplon / meteor / mobx 和 Elm / ReactiveX 基本没有关系。
我们知道,spreadsheet(别提到 spreadsheet 就说 excel,这世界的 spreadsheet 不光是 excel 一家,还有死去的 Lotus,半死不活的 Numbers,如日中天的 Google spreadsheet 呢)是一门古老而又强大的开发工具,很多时候,一些工具用 spreadsheet 几分钟就能搞定,而找个程序员做,不但没几天做不出来,扩展性肯定是要差不少,性能也未必会好,还动不动出 bug。不信我给你出道题:把 google analytics 里的数据(session,DAU等)定期 pull 出来,对比本周和上周,本月和上月,本年和上年的数据,并将其用图表可视化出来,做成一个 dashboard,在座的各位,可以估计一下,需要多少工时?
如果用 google spreadsheet,完成这个功能也就是十分钟的事情。
spreadsheet 的强大之处在于:你可以定义一系列输入(源数据,source data),然后定义任意层次的依赖这些数据的公式(衍生数据,derived data),直至你需要的结果(数据或者图表)。一个中间过程可以依赖任意多个其它数据,不管其是 source data 还是 derived data,spreadsheet 会自动帮你记录并计算这种依赖关系。当源数据变化的时候,这种变化像石头扔进湖里产生的涟漪一样,扩散到所有的何其有关的 衍生数据。你可以有成百上千个源数据,可以有成千上万的衍生数据,spreadsheet 都能高效快速正确地处理数据变化产生的一切影响。
其实我们平时写代码,很多时候也是把原始数据衍生出来各种各样的数据,展现出来各种各样的结果。比如说经典的 TodoMVC,源数据是一个个 todo item,衍生数据是:todo item 的数量,未完成的 todo item 的数量,以及在页面上可视化的 todo items。一个 TodoMVC 的小 app,用 spreadsheet 做,再简单不过,几个公式就可以搞定;而用层出不穷的各种前端框架,没有数百行代码是无法做好的 —— 因为 spreadsheet 是自动管理依赖关系的,而几乎所有的编程语言和框架,都需要程序员手工处理数据间的依赖关系。
spreadsheet 是上世纪八十年代的产物,三十多年过去了,软件开发领域已经天翻地覆,新语言,新概念层出不穷,为什么没有产生一个好的 library 或者 framework,甚至是某个语言,能像 spreadsheet 那样,只消定义哪些数据是源数据,哪些是衍生数据,然后底层的架构帮助管理 数据间的 dependency,当源数据发生改变,这种改变能够自动传播到所有衍生数据上面,既不出错,又很高效?
我所知道的业界的第一个答案是:hoplon,这个在两年前 clojure west 大会上大放异彩的 framework,大家有兴趣可以看这个 video: https://www.youtube.com/watch?v=wVXjExRiFy0。hoplon 使用 clojure 的 metaprogramming 的能力,引入了 spreadsheet 中 cell 和 formula 的概念,你可以使用 cell 定义源数据,用 cell= 定义衍生数据,hoplon 会帮助你处理 dependency tree,当源数据变更时,重新计算相关的衍生数据。
hoplon 的概念很好,但 clojure 毕竟还算是小众语言,所以虽然我发现它之后激动地写了好几个小程序,但没有在生产环境中真正用过。
hoplon 可能很多人都很陌生,但 meteor 估计大部分同学都知道。虽然 meteor 没有把其 reactive 的思想和 spreadsheet 联系起来,然而其内部实现的思想也是类似:meteor 有 reactive source,和 reactive computation 的概念,meteor 的 session 变量,database collection 都是 reactive source,而 template helper 是 reactive computation。明白了这些,meteor 也没有什么特别神秘的。
javascript 领域的另一颗新星:mobx,则站在前辈们的肩膀上,把 spreadsheet-like data flow 做得更加出色。mobx 背后的公司似乎是在做前端的绘图工具时不胜其扰,开发了这个 library。我们知道,类似 vision 的绘图工具,里面有太多太复杂的状态,往往牵一发而动全身:比如你有一个节点 A 和其他 N 个节点有连接,当你拖动 A 时,这些连接的位置都需要相应变动,甚至会影响其他 N 个节点的位置和状态,从而进一步影响更多的节点。如果不能自动处理这种数据变更的扩散,而手工处理的话,代价很大,效果很差。
为了自动处理状态的变化的传播,mobx 定义了这些概念(请自行和 meteor / hoplon 对比):
- observable state:源数据。注意不要和 ReactiveX 的 Rx.Observable 混淆了。
- computed state:衍生数据,类似于 spreadsheet 里的公式。不能有副作用。
- reaction:类似于 computed state,可以有副作用。
- action:外界的各种 event(导致 observable state 改变)。
mobx 处理 state propagation(扩散)的方式很赞,可以自行看我的 slides,想想为什么要做得这么复杂,有什么好处(尤其是 dependency tree 非常复杂的情况)。所以其 performance 很好。
mobx 另外一个好处是其对 react 的支持非常好,简直是量身定制 —— 想想 flux 你要做多少 bolerplate code,才能让状态在 data store 和 view 之间无缝同步,而使用 mobx,你只需要对 data store 的源数据使用 observable decorator(对,decorator 是 ES7 的语法,但我们不是有伟大的 Babel 么?),对一些产生衍生数据的 helper function 使用 computed decorator,然后在 react component 定义时使用 observer decorator,基本就可以了。相信随着 mobx 的发展,以及越来越多的人认识到由系统自动处理状态的扩散要好过自己手工处理,mobx 会赶超 redux,在 react 生态圈占据重要的席位。想想看:react(view)+ mobx(state management)+ rxjs(event streaming),这画面要不要这么美啊!
好,废话不多说,咱们看图不说话。