为什么我从React转为Cycle.js

原文来自 sitepoint.com/switching 。欢迎评论区留言,交流技术研发问题。

我在想现在应该有很多的开发人员在使用一些现成的框架进行app开发。框架确实帮助我们来搭建复杂的app从而节约我们的时间。每天我们都能听到许多关于框架的讨论,什么框架是最好的,我们应该先学那种框架,等等。因此今天,我将会和大家分享我的经验,关于我为什么从React框架转到Cycle.js。

React 目前或许是最流行的前端框架,并且它有一个很大的社区。我非常喜欢它,它也确实帮助改变了我对web程序的看法,以及我开发程序的方式。有许多的开发者非常喜欢用react ,但是也有些开发人员认为它并没有像别人说的那样好。

大多数人使用React的时候,并没有想到或许会有一种更好的方式去开发程序。在这种情况下使我想去尝试一下Cycle.js,一个新的响应式框架正在变得越来越流行。在这篇文章中,我会解释什么是响应式编程,Cycle.js如何工作的,以及为什么我会觉得它比React更好。那现在我们就开始吧。

什么是响应式编程

响应式编程(RP) 是一种异步数据流编程。如果你已经建立了一个网站程序,那么你或许已经做了很多响应式编程的工作。比如,点击事件就是异步数据流。我们可以监听事件,通过这个事件做一些响应。RP背后的思想是让我们有能力从任何情况创建数据流,从而操作他们。然后我们可以对所可能的响应结果抽象成相同的概念,从而来更容易使用、维护、测试。

也许你会在想" 为什么我需要这种新的响应式编程"。答案很简单:响应式编程将会帮助你统一代码,使代码更加一致。你不需要知道代码是如何工作的,也不用知道如何正确地实现他们。仅仅只需要用相同的方式写代码,不管你正在处理什么样的数据(单击事件,HTTP调用,Web套接字...)。每一个事情都是一个数据流,每一个数据流都有很多功能,你都可以去使用它,比如map 和filter。这些功能将会返回新的能够使用的流,等等。

响应式编程对你的代码给予更大的抽象。它会让你能够创造交互式的用户体验,主要关注在业务逻辑。




图片来自gist.github.com/staltz/

响应式编程在JavaScript中的应用

在JavaScript 中,我们有很多用于处理数据流的库。最负盛名的一个就是RxJS。它是 ReactiveX的扩展,一个具有可观察流的异步编程API。你可以创建一个可监听的流数据,使用不同的函数执行它。

其次就是Most.js 。它有最好的性能,已经通过一些数字来证明了:性能比较。

我也想提到一个小又快的库,Cycle.js的开发者专门编写的。它就是xstream。它只有26个方法,大约30kb大小,是js中响应式编程是最快的库之一。

在下面的例子中,我将使用xstream库。Cycle.JS 被做成了一个小框架,我想把它和最小的响应式库结合起来使用。

何为Cycle.js

Cycle.js 是一个函数式和响应式的JavaScript 框架。它把你的程序抽象成一个纯函数,main()。在函数编程中,函数只有输入和输出,没有其他响应。在Cycle.js的 main()函数中,输入是从外部世界读结果(源),输出是向外部世界写结果。用驱动程序来管理响应。驱动程序是用来管理DOM 结果,HTTP结果,和web套接字,等等的插件。





图片来自Cycle.js网站

Cycle.js 是用来帮助我们建立自己的用户界面,测试并且写出可重复使用的代码。每个组件仅仅是一个可以独立运行的纯函数。

核心API是一个函数,运行。

run(app, drivers);

它有两个参数,app和drivers 。 app是主函数,drivers是需要处理结果的插件。

Cycle.js把另外的功能分成了一些小的模块,它们是:

@cycle/dom – a collection of drivers that work with DOM; it has a DOM driver and HTML driver, based on the snabdom virtual DOM library 
@cycle/history – a driver for the History API 
@cycle/http – a driver for HTTP requests, based on superagent 
@cycle/isolate – a function for making scoped dataflow components
@cycle/jsonp – a driver for making HTTP requests through JSONP 
@cycle/most-run – a run function for apps made with most 
@cycle/run – a run function for apps made with xstream
@cycle/rxjs-run – a run function for apps made with rxjs

Cycle.js 代码

让我们先来看看一些Cycle.js源码。我们将会创建一个小的程序来演示它是如何工作的。我想一个计数器程序将会是一个理想的例子。我们将会在看到Cycle.js如何操作DOM事件和重新渲染DOM。

让我们先来创建两个文件,index.html 和 main.js。 index.html只提供给main.js文件,main.js 是我们的逻辑所在。我们也要新建一个package.json 文件。运行:

npm init -y

接下来,我们安装主要的依赖:

npm install @cycle/dom @cycle/run xstream --save

这条命令将会安装 @cycle/dom, @cycle/xstream-run, xstream. 我们也需要 babel, browserify and mkdirp 。用以下命令安装:

npm install babel-cli babel-preset-es2015 babel-register babelify browserify mkdirp --save-dev

为了配合Babel的工作,需要用以下内容创建一个.babelrc文件:

{
  "presets": ["es2015"]
}

为了运行我们的程序,我们也需要在package.json中添加脚本:

"scripts": {
  "prebrowserify": "mkdirp dist",
  "browserify": "browserify main.js -t babelify --outfile dist/main.js",
  "start": "npm install && npm run browserify && echo 'OPEN index.html IN YOUR BROWSER'"
}

启动我们的Cycle.js程序,我们运行命令 npm run start。

大致步骤就这样。我们的项目设置已经完成,现在我们可以写代码了。让我们先在index.html里添加一些HTML代码:

< !DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Cycle.js counter</title>
</head>
<body>
    <div id="main"></div>
    <script src="./dist/main.js"></script>
</body>
</html>

我们已经创建了一个id为main的div。Cycle.js将会连接到这个div,然后渲染到整个整个程序。我们也引用了dist/main.js文件。这是main.js在打包编译后的js。

说了这么多该去写些Cycle.js代码了。打开main.js文件,导入我们需要的依赖:

import xs from 'xstream';
import { run } from '@cycle/run';
import { div, button, p, makeDOMDriver } from '@cycle/dom';

我们引入了xstream, run, makeDOMDriver,这些函数将会帮助我们在虚拟Dom上工作(div,button 和p元素)。

让我们写主函数吧,它大概长这样:

function main(sources) {
  const action$ = xs.merge(
    sources.DOM.select('.decrement').events('click').map(ev => -1),
    sources.DOM.select('.increment').events('click').map(ev => +1)
  );

  const count$ = action$.fold((acc, x) => acc + x, 0);

  const vdom$ = count$.map(count =>
    div([
      button('.decrement', 'Decrement'),
      button('.increment', 'Increment'),
      p('Counter: ' + count)
    ])
  );

  return {
    DOM: vdom$,
  };
}

run(main, {
  DOM: makeDOMDriver('#main')
});

这就是我们的主函数。它拿到 sources,返回sinks 。sources是DOM流,sinks 是虚拟DOM。让我们一点一点分析这段代码。

const action$ = xs.merge(
  sources.DOM.select('.decrement').events('click').map(ev => -1),
  sources.DOM.select('.increment').events('click').map(ev => +1)
);

这里我们把两个流合并成一个流,命名为action$(根据规则去命名一个含有数据流的变量时要添加后缀$)。这两个流一个是在decrement按钮上点击流,另一个是在increment按钮点击流。 我们把这两个事件分别映射成数字-1和+1。在合并的最终,action$流是这个样子的:

----(-1)-----(+1)------(-1)------(-1)------

下一个流是count$,代码如下:

const count$ = action$.fold((acc, x) => acc + x, 0);

fold 函数在这段代码功能强大。它接受两个参数,accumulate 和seed。seed 在事件响应时是第一个被提交的。下个事件在accumulate函数的基础上联合seed 。它基本上是reduce()流。

我们count$ 流接受 0 作为初始值,然后在action$流中新值的基础上,我们在count$ 中把它与当前值求和。

最后,为了是程序工作,我们需要调用mian下的run函数。

最后一件事情是创建虚拟DOM,代码如下:

const vdom$ = count$.map(count =>
  div([
    button('.decrement', 'Decrement'),
    button('.increment', 'Increment'),
    p('Counter: ' + count)
  ])
);

我们在count$流中映射数据,在流中给每个item都返回一个虚拟DOM。虚拟DOM包含一个主div包装器,两个按钮,一个段落。正如你所看到的那样,Cycle.js 使用js函数来处理DOM ,JSX也可以被处理。

在main函数的最后,返回我们的虚拟DOM:

return {
  DOM: vdom$,
};

我们在通过main函数和一个DOM驱动,该驱动连接到id为main的div,从而从div中得到事件流。我们现在关闭我们的圈子,只做更完美的Cycle.js 应用程序。

以上就是你如何处理DOM流的。如果你想看看HTTP流如何在Cycle.js工作的,我已经写了关于这个的一篇文章(在我的博客)[ivanjov.com/working-wit]。

我已经把所有的代码都推送到Github repo 。 把代码check下来,试着在你本地机器上运行。

为什么我会从React 转向Cycle.js?

现在你已经知道响应式编程的基本概念,也看到关于Cycle.js的一个简单例子。现在我来谈谈我为什么把Cycle.js应用到我的下个项目中。

当我在设计网站程序时,我遇到最大的问题是如何操作大型代码库和来自不同的地方的大批量数据。我是React的热衷粉丝,我曾经在我的很多项目中使用它,但是它并没有解决我遇到的这个问题。

React工作非常灵活在处理渲染数据和改变程序状态时。实际上,整个组件方法带给人感觉很惊奇,它也确实帮助我写出更完美,可测试和可维护的代码。但是总感觉还是少了些什么。

让我们通过React看看在使用Cycle.JS的一些pros和cons。

pros

1.大型代码库

当你的程序变得越来越大时,这是就会发现React有一些问题出现了。假如你100个容器里有100个组件,每个组件都有自己的样式、函数和测试用例。这样就会有很多的目录,每个目录下有很多文件,每个文件里有很多行代码。到这里你大概就是我的意思了,React定位到这些文件里是有些困难的。

2、数据流

在React我遇到最大的问题就是数据流。React 设计时并没有把数据流作为核心考虑进去。 开发者试着去解决这个问题,所有我们有很多库和方法论,利用这些去解决这个问题。React中最核心的是 Redux , 但是它也不完美。 你需要花一些时间去配置它,也需要写一些代码来处理数据流。

所以Cycle.js的诞生就是为了解决这-问题。创建者希望创造一个框架能够替使用者已经考虑到数据流相关的东西。你只需要写一些函数来操作数据,因为Cycle已经帮你做好了处理。

3、响应结果

React在处理响应结果时也有问题。在React 程序中并没有什么标准方式来处理响应结果。虽然这里有很多的工具可以帮助处理这个问题,但是需要花费一些时间来设置和学习如何使用他们。其中最有名的几个是 redux-saga, redux-effects, redux-side-effects, 和 redux-loop。你明白我说的了吧。这里有很多的库,你可以选择一个来执行你的代码。

Cycle.js 并不需要这样做。你只需要引用你需要的驱动(DOM, HTTP或者其他),然后就可以直接使用了。这个驱动向你的函数里发送数据,你可以修改数据,再把它发送回驱动,就可以将数据渲染或者做其他事情了。最重要的是,它是标准化的,它是Cycle.JS自带的,你不需要依赖任何第三方库。Cycle.JS 就是这么简单。

4、函数式编程

最后但同样重要的是函数式编程。React 创造者声称React 使用了函数式编程但是这并不是真的。这里有许多OOP、classes、this 关键字,你将会很头痛如果你没有正确使用这些。Cycle.JS在设计时就加入了函数式编程范式在理念里。每件事都是一个不依赖外在状态的函数。同时,这里也没有类或者像类这样的概念。这样Cycle.JS更容易去使用和操作。

Cons

1、社区

目前, React 是一个非常流行的框架,它被应用到很多程序上。但是Cycle.JS 不是这样,它依然不为大多数人所知,这将是一个问题,比如你遇到一些意外的情况可能会找不到一个解决方案来解决这个问题。有时候你在网上找不到答案,只能靠你自己了。当你做一些边缘项目有很多充足的时间时这并不是一个问题,但是如果你在做公司的项目并且有很短的排期呢?你将没有时间来debug你的代码。

但是这些都在改变。很多的开发人员开始使用Cycle.JS和讨论在使用它时遇到的问题,大家一起集思广益解决。Cycle.js 的使用文档讲的很仔细,上面有很多的例子。目前我还没有遇到很复杂无法解决的问题。

2、学习一个新的范式

响应式编程是一个不一样的范式,你需要花点时间知道它的原理。明白原理后,一切都变得简单了。但是如果你的时间很紧,那么学习新的知识将会很有问题。

3、一些程序并不需要响应式

是的,一些程序真的不需要去做成响应式的。博客、购物网站、登录页面和其他有限制响应的静态网站,这些都不需要做成响应式。在实际上,这些程序里没有那么多数据,也没有那么多表格和按钮。使用响应式框架将会降低我们网站的响应速度。所以你需要评估一下你的网站程序是否真的需要使用Cycle.JS。

总结

一个理想的框架将会帮助你只关注制作和交付特性,不会强制你去写一些公式化代码。我想Cycle.JS 已经展现给我们这是可能的,让我们可以寻找更好的方法去写代码和交付的结果。但是,任何事都是不完美的。这里还有很多有待提升的空间。

你有尝试过响应式编程或者Cycle.JS吗?我说了这么多,真的可以试一下喔。可以在评论中给我留言,欢迎大家积极讨论。

本文由Michael Wanyoike同行评议。 感谢SitePoint的同行评议人员,使SitePoint的内容能够成为优质内容!

编辑于 2017-07-21

文章被以下专栏收录

    定期推送资深工程师的原创文章,话题以前端为主,当然也会涉及到后端以及其它话题,重要的是一定都是原创。