使用流的思维方式来构建cycle.js前端应用

流是在时间流逝的过程中产生的一系列事件的集合,和数组的概念类似,只不过流的索引是时间,它的值是事件。一个流或者一系列流可以作为另一个流的输入, 流在rxjs,xstream,most等库中是一等公民,而学习流最难的地方就在于思维方式的转变:一切皆流!

使用流来构建应用的优点主要有:

  • 容易书写
  • 进一步抽象
  • 容易阅读
  • 容易测试
  • 写起来爽

适用的场景主要有:

  • 比较复杂的异步场景
  • 有大量基于异步数据流的操作
  • 有大量交互
  • 有很多回调深渊
  • 实时响应

什么是流?

流主要用于异步数据流,我们平常所用的事件,比如click事件,input事件,http请求等这些都是异步数据流,不只是这些,我们可以为任何东西创建一个流,比如变量, 用户的输入, 属性, 缓存, 数据结构,所有的东西都可以创建为流来使用。比如说我们的朋友圈的时间轴的数据就可以是一个数据流,各个业务都可以订阅这个流做出相应的自己的逻辑。

用来创建和使用流的工具比如rxJs,xstream,most(按从重到轻排序)等流行的库均提供了一系列创建、合并、过滤、组合流的方法,并且这些方法都是函数式的!

一个流可以作为另一个流的输入,流是流世界里的一等公民,他可以emit出3种类型的信号,next,error,complete,订阅者可以在这些回调的方法里做对应的处理。

流是一个被观察者,观察者可以通过定义回调函数来监听它,流有点像是一个增强版的promise,区别在于promise是定义之后就执行,但是对于流来讲,必须有人订阅它,才会执行。promise的then只会被回调一次,而流的next可以被无限次回调,流可以实现promise能实现的所有的功能。

我们一般用$符结尾的变量名来命名流,表示其是一个可观测对象。

接下来我们从几个小例子中开始学习一下流吧:

//从dom事件中创建流
import fromEvent from 'xstream/extra/fromEvent'

const domInput$ = fromEvent(document.querySelector("#input"), 'input');

//value流,这个流的输出是input的value,输入是domInput事件流
const value$ = domInput$.map((event) => {
    console.log('event', event);
    return event.target.value;
});

//test流,这个流的输入是value流,输出是当value流里的值是'test'的时候
const test$ = value$.filter((data) => {
    console.log('data',data);
    return data === "test"
})

//每个返回都是一个新的流,流是immutability的

//订阅test流
test$.subscribe({
     next:(data)=>{
         console.log('after filter',data);
     }
 }) 

//我们也可以从promise中创建流
const fromPromise$ = xs.fromPromise(new Promise((resolve, reject) => {
    resolve("test");
}))

//我们可以合并test流和promise流
const allTest$ = xs.merge(fromPromise$, test$);

/*
//合并流方法执行的原理:

stream A: ---a--------e-----o----->
stream B: -----B---C-----D-------->
        vvvvvvvvv merge vvvvvvvvv
        ---a-B---C--e--D--o----->
*/

//订阅合并流
allTest$.subscribe({
    next:(data)=>{
        console.log('test',data);
    }
})

//下面是最原始的创建流的方法

const stream$ = xs.create({
    start: function (listener) {
        let i = 1;
        this.id = setInterval(() => {
            listener.next(i++)
        }, 1000);
    }, //当有订阅者订阅的时候才开始执行,懒执行的
    stop: function (listener) {
        clearInterval(this.id);
    }, //当订阅者取消订阅时,会回调这个stop
    id: 0
})

cycle.js是一个基于函数式和适用于处理异步数据流的js框架.

比如点击事件,http请求事件这些都是异步的数据流,在cycle.js里开发者可以观测这些数据流,并用特定的逻辑对它们进行处理。cycle.js默认使用xstream作为流的基础库,基于xstream你可以创建任意流。基于流的概念,xstream赋予了一系列对流进行操作的函数工具集,使用这些函数可以合并、创建、过滤这些流。 一个流或者一系列流可以作为另一个流的输入。你可以合并两个流,从一堆流中过滤你真正感兴趣的那一些,或者将值从一个流*映射*到另一个流。

cycle.js提出了Model-View-Intent模型

它遵循的理念:

  • 一切都是事件源
  • 使用流的概念来构建整个应用的模型
  • 使用driver来隔离有副作用的行为(如网络请求、DOM渲染、URL变化)

基于这套理念,我们的代码就像下面这样:

  • 从driver流映射成action流
  • 把action映射成数据流
  • 把数据流映射成界面流

用通俗的话来讲就是,这个模型是无副作用的,dom或其他的输入作为入参,它给你输入啥,你对应的做处理,最后输出一个相应的dom就可以了。用代码里表示就是:

/**
@source里可以有DOM,HTTP等外界的事件来源
@最终根据DOM的变化处理成一个虚拟dom返回就ok
*/
function main(sources) {
    const actions = intent(sources.DOM);
    const state = model(actions);
    const vDom = view(state);
    return {DOM: vDom};
}