在学习过程中最困难的一部分是 以RP的方式思考 。这意味着要放弃命令式且带状态的(Imperative and stateful)编程习惯,并且要强迫你的大脑以一种不同的方式去工作。在互联网上我找不到任何关于这方面的教程,而我觉得这世界需要一份关于怎么以RP的方式思考的实用教程,这样你就有足够的资料去起步。库的文档无法为你的学习提供指引,而我希望这篇文章可以。
一方面,这并不是什么新东西。Event buses或者Click events本质上就是异步事件流(Asynchronous event stream),你可以监听并处理这些事件。RP的思路大概如下:你可以用包括Click和Hover事件在内的任何东西创建Data stream(原文:"FRP is that idea on steroids. You are able to create data streams of anything, not just from click and hover events.")。Stream廉价且常见,任何东西都可以是一个Stream:变量、用户输入、属性、Cache、数据结构等等。举个例子,想像一下你的Twitter feed就像是Click events那样的Data stream,你可以监听它并相应的作出响应。
既然Stream在RP中如此重要,那么我们就应该好好的了解它们,就从我们熟悉的"Clicks on a button" Event stream开始。
Stream就是一个 按时间排序的Events(Ongoing events ordered in time)序列 ,它可以emit三种不同的Events:(某种类型的)Value、Error或者一个"Completed" Signal。考虑一下"Completed"发生的时机,例如,当包含这个Button(指上面Clicks on a button"例子中的Button)的Window或者View被关闭时。
在Rx*中, combineLatest 似乎实现了我们想要的功能。它接受两个Stream,A和B作为输入,当其中一个Stream emit一个值时, combineLatest 把最近两个emit的值 a 和 b 从各自的Stream中取出并且返回一个 c = f(x,y) , f 为你定义的函数。用图来表示更好:
还有一个问题需要解决。combineLatest()使用最近的两个数据源,但是当其中一个来源没发起任何事件时,combineLatest()无法在Output stream中产生一个Data event。从上边的ASCII图中,你可以看到,在第一个Stream emit a 这个值时并没有任何输出产生,只有当第二个Stream emit b 时才有值输出。
留意一下代码中并没有出现如 if 、 for 、 while 这样的控制语句,或者一般JavaScript应用中典型的基于回调的控制流。如果你想使用 filter() ,上面的subscribe() 中甚至可以不用 if 、 else (实现细节留给读者作为练习)。在RP中,我们有着像 map 、 filter 、 scan 、 merge 、 combineLatest 、startWith 这样的Stream函数,甚至更多类似的函数去控制一个事件驱动(Event-driven)的程序。这个工具集让你可以用更少的代码实现更多的功能。
如果你觉得Rx*会成为你首选的RP库,花点时间去熟悉这个函数列表,包括了如何转换(transform)、合并(combine)、以及创建Observable。如果你想通过图表去理解这些函数,看一下这份 RxJava's very useful documentation with marble diagrams 。无论什么时候你遇到问题,画一下这些图,思考一下,看一下这一大串函数,然后继续思考。以我个人经验,这样效果很明显。
一旦你开始使用Rx*去编程,很有必要去理解 Cold vs Hot Observables 中的概念。如果忽略了这些,你一不小心就会被它坑了。我提醒过你了。通过学习真正的函数式编程(Funational programming)去提升自己的技能,并熟悉那些会影响到Rx*的问题,比如副作用(Side effect)。
但是RP不仅仅有Rx*。还有相对容易理解的 Bacon.js ,它没有Rx*那些怪癖。 Elm Language 则以它自己的方式支持RP:它是一门会编译成Javascript + HTML + CSS的RP 语言 ,并有一个 Time travelling debugger 。非常NB。