浅尝辄止的了解下RxJS/Cycle.js(一)

Published: 2016-02-03

Tags: RxJS Cycle.js

本文总阅读量

在推闲逛,看到有人转了个Cycle.js的视频教程

Cycle.js是什么东西?

经过一番谷歌,这个东西诞生的也不算太久,灵感应该来自于React,因为它的思路实在是和React太像了...可以看下这个Demo -> http://jsbin.com/vikaga/1/edit?js,output

一篇文章作者这样描述Cycle.js

Cycle.js is clearly React inspired, but it attempts to fix some areas where React is deficient, or weird. It also advocates (forces?) a functional style, composing immutable state via observables.

Cycle.js的官网是这样介绍的

A functional and reactive JavaScript framework for cleaner code

Cycle.js支持Virtual-DOM、服务端渲染、JSX、React Native、等等

看Cycle.js的Github上的介绍说:Note: rx is a required dependency. Without it, nothing will change.,而且我看Cycle的视频教程第一课也码的是Rx,这是什么?

好吧,最开始我为了图方便,直接百度RxJS,出来仅有的几个结果简直是让人绝望...

还是问问谷大哥好了,RxJS是微软出品的ReactiveX的一个Javascript语言子集,Rx可以使用相当多的编程语言和平台

ReactiveX是Reactive Extensions的缩写,一般简写为Rx,最初是LINQ的一个扩展,由微软的架构师Erik Meijer领导的团队开发,在2012年11月开源,Rx是一个编程模型,目标是提供一致的编程接口,帮助开发者更方便的处理异步数据流,Rx库支持.NET、JavaScript和C++,Rx近几年越来越流行了,现在已经支持几乎全部的流行编程语言了,Rx的大部分语言库由ReactiveX这个组织负责维护,比较流行的有RxJava/RxJS/Rx.NET,社区网站是 http://reactivex.io/

好吧,看来想学Cycle.js得先知道Rx是什么,Rx上面已经知道是微软出的一个处理异步数据流的库,其实,它也是RP的一个实现。RP(Reactive Programming, 响应式编程),是个挺有趣的东西,他的实现有Rx、Bacon.js、Kefir等等,好吧,概念一堆堆的出来,压力稍微有点大...先看看wiki上对于RP的简短解释:响应式编程

说了这么多,这些和FB出的React又是什么关系?

我是这么理解的,他们流派不同,都是响应式编程的一种解决方案,FB的React有Flux,然后RxJS也可以搭配Cycle.js实现相同的事情,都是在做基于数据流的编程(此处比较慌...自己理解,大神路过,如果错误请斧正),关于响应式编程,可以看看这篇文章:RP入门,文章里还实现了一个Twitter Follow的Demo,相当不错:http://jsfiddle.net/staltz/8jFJH/48/

铺垫太多了,其实我就是想记录两个RxJS的小Demo而已的...回归主线

第一次看RxJS的代码是这样的

// Logic
Rx.Observable.timer(0, 1000)
  .map( i => `seconds elapsed ${i}`)
// Effects
  .subscribe(text => {
    const container = document.querySelector('#app');
    container.textContent = text;
  });

配合上index.html文件

<!DOCTYPE html>
<html>
<head>
<script src="//cdn.bootcss.com/rxjs/4.0.7/rx.all.min.js"></script>
<script src="/1.js"></script>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>RxJS test</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>

用浏览器打开,页面就会从零开始计数,1s加一

这种数据流的方式感觉还是满亲切的,跟Unix的管道概念很是类似,在Rx中的Observable对象也跟Unix的'一切皆文件'有些神似。很难想象是微软竟然搞出的东西,隐隐感觉这Rx要火呢...

其实第一个例子挺不好的,耦合度很高,小小改进一下

// Logic (functional)
function main() {
  return Rx.Observable.timer(0, 1000)
  .map( i => `seconds elapsed ${i}`);
}

// Effects (imperative)
function DOMEffect(text$) {
  text$.subscribe(text => {
    const container = document.querySelector('#app');
    container.textContent = text;
  });
}

DOMEffect(main());

这下把逻辑和显示分开了

不过还是功能写死了,逻辑太单一了,只能输出到界面上,再稍稍改进一下,使其能够同时输出到console里

function main() {
  return {
    DOM: Rx.Observable.timer(0, 1000)
      .map(i => `Seconds elapsed ${i}`),
    Log: Rx.Observable.timer(0, 2000)
      .map(i => 2*i),
  };
}

function DOMEffect(text$) {
  text$.subscribe(text => {
    const container = document.querySelector('#app');
    container.textContent = text;
  });
}

function consoleLogEffect(msg$) {
  msg$.subscribe(msg => console.log(msg));
}

function run(mainFn) {
  const sinks = main();
  DOMEffect(sinks.DOM);
  consoleLogEffect(sinks.Log);
}

run(main);

经过改进,可以定义不同的效果函数,来展示数据,还是不错的,只是仔细想来,Effect这种叫法不是太准确,使用Driver恰当很多,再者一个问题是,这个程序还是不够灵活,如果想要注释掉一个效果,比如在consoleLogEffect(sinks.Log);前面加上注释,这么做没问题,不过有更好的方法,来控制哪个Driver运行

function main() {
  return {
    DOM: Rx.Observable.timer(0, 1000)
      .map(i => `Seconds elapsed ${i}`),
    Log: Rx.Observable.timer(0, 2000)
      .map(i => 2*i),
  };
}

function DOMDriver(text$) {
  text$.subscribe(text => {
    const container = document.querySelector('#app');
    container.textContent = text;
  });
}

function consoleLogDriver(msg$) {
  msg$.subscribe(msg => console.log(msg));
}

function run(mainFn, drivers) {
  const sinks = mainFn();

  Object.keys(drivers).forEach(key => {
    drivers[key](sinks[key]);
  });
}

const drivers = {
  DOM: DOMDriver,
  Log: consoleLogDriver,
}

run(main, drivers);

这样,只需在drivers中进行注释就可以决定哪个运行,比较清晰合理

其实,最近学了几个例子,我感觉Rx我才接触到冰山一角,别说深入一些的思想,就是常用函数和功能都还没搞太清楚...不过,还是要硬着头皮整理一下,不然过一阵还没往下学习就又忘光了...

所以,这里我不知道Rx最适合做什么,也不知道Rx有有什么黑科技,反正Rx很强大,慢慢往下看Demo吧...

RxJS很核心的一个东西就是Observable,它是一个可观测对象,它可以自己创建,可以由事件,数据,甚至能接受Promise对象,比如:

var source = [1, 2, 3, 4, 5];

var source = Rx.Observable.fromArray([0, 1, 2, 3, 4, 5]);

source.filter(x => x % 2 === 1)
  .map(x => x + '!')
  .forEach(x => console.log(x));

输出为

1!
3!
5!

再比如

console.clear();

var source = Rx.Observable.interval(500).take(10);

source.filter(x => x % 2 === 1)
  .map(x => x + '!')
  .forEach(x => console.log(x));

输出为

1!
3!
5!
7!
9!

在Rx中,使用好数据流,可以在不使用if/else实现判断,有趣的很

有一本Online Book叫做《rx-book》,是个参考书,可以收藏查阅,并且里面的函数都是提供Demo的,使用的是jsbin,可以修改看效果,很直观方便

再来看看最开始的那个例子,其实subscript中是可以接受参数的,看下面的例子

Rx.Observable.timer(0, 1000)
  .map( i => `the time is ${i}`)
  .subscribe(text => {
    const container = document.querySelector('#app');
    container.textContent = text;
  }, function(err) {
    console.error(err);
  }, function() {
    console.info('done');
  });

在这个中,第一个函数进行控制数据行为,第二个函数进行错误处理,第三个函数在所有的数据都处理完成时输出done

整体而言,首先用Rx创建一个Observable可观察对象,然后将其传给source变量,在Rx中有个叫Observer的对象,是用来专门定义上边代码的中subscribe中的行为函数的,比如

var source = Rx.Observable.return(42);

var observer = Rx.Observer.create(
  x => console.log(`onNext: ${x}`),
  e => console.log(`onError: ${e}`),
  () => console.log('onCompleted'));

var subscription = source.subscribe(observer);

// => onNext: 42
// => onCompleted

这样,结构更加清晰合理了。当然,在Observable的后面,我们使用fromArray([0, 1, 2, 3, 4, 5]);就输出1,2,3,4,5序列了。更多内置的方法,需要去查阅上面提供的rx-book进行寻找了~

比如这里:http://xgrommx.github.io/rx-book/content/observable/observable_methods/index.html可以找到很多创建Observable的方法,这里http://xgrommx.github.io/rx-book/content/observable/observable_instance_methods/index.html可以找到很多Observable的对象的方法,可以对数据进行过滤,聚合,拆分,等等操作,就像管道一样,这种处理数据的方式还是蛮Cool的

不知道读者注意到上面提到的done,对了,反正我翻看rx-book中Mapping RxJS from Different Libraries很是吃惊...前几个月学习过的Async流程控制和集合操作竟然可以等效的转移到RxJS...详见:RxJS for Async.js Users

比如async中的each操作是这样的:

var async = require('async'),
    fs = require('fs');

var files = ['file1.txt', 'file2.txt', 'file3.txt'];

function saveFile (file, cb) {
    fs.writeFile(file, 'Hello Node', err => cb(err));
}

async.each(files, saveFile, err => {
    // if any of the saves produced an error, err would equal that error
});

在RxJS中可以这么实现

var Rx = require('rx'),
    fs = require('fs');

var files = ['file1.txt', 'file2.txt', 'file3.txt'];

// wrap the method
var writeFile = Rx.Observable.fromNodeCallback(fs.writeFile);

Rx.Observable
    .for(files, file => writeFile(file, 'Hello Node'))
    .subscribe(
        () => console.log('file written!'),
        err => console.log(`err ${err}`),
        () => console.log('completed!')
    );

我此时内心是十分惊讶的...

M¥出品的东西果然是高大上,相比于各种辅助类的工具,Rx感觉像是一个庞然大物,不是一个运用于快速构建的工具,而更像是一个工程级的异步数据流解决方案,在rx-book中还有很多东西没有看,没有深入的学习也不是很了解,不过简单的对比,我感觉React更适合快速开发,随着React Native的发展,也将在移动端大放异彩。而Rx给人一种更加专业化的感觉,作为响应式编程的先行者,作为众多同类技术的灵感来源,Rx是很强大的,在其开源之后,各种语言环境的子分支都有不错的发展,而这也是很难能可贵的。

今天的整理就到这里,至于Cycle.js, 下篇同类型的博文再记录吧...

参考