文档结构  
翻译进度:已翻译     翻译赏金:10 元 (?)    ¥ 我要打赏

我想现在大多数开发者都在使用某种类型的框架来开发应用程序。框架可以帮助我们构建复杂的应用程序,并为我们节省时间。我们每天都能看到许多关于什么框架是好的讨论,以及你应该先学习哪个框架,等等。今天,我想分享自己的经验,为什么从 React 转向 Cycle.js。

近来,React 可能是最流行的前端框架,它拥有强大的社区。我非常喜欢它,而且它也确实帮助我改变了对开发 Web 应用的看法。有一些开发者喜欢它,还有一些人认为它不如大家所说的那么好。

 

第 1 段(可获 1.59 积分)

多数人在开始使用 React 的时候并没有思考可能有更好的方式来构建 Web 应用。而这些思考让我决定尝试 Cycle.js,它是是一个新的响应框架,如今正变得越来越流行。本文中,我想说清楚什么是响应式编程,Cycle.js 如何运作,以及为什么我会认为它比 React 更好。开始吧!

什么是响应式编程?

响应式编程 (Reactive Programming, RP) 是一种使用异步数据库的编程方式。如果你已经创建过 Web 应用,你可能已经进行了大量的响应式编程。比如,点击事件就是一种异步数据流。我们可以观察它们并执行一些副作用。RP 背后的想法是让我们有能力从任何东西创建数据流,然后操作它们。我们对所有副作用都有相同的抽象,这样就能更好的使用、管理和测试它们。

 

第 2 段(可获 1.9 积分)

你可能会想“我为什么需要这种新兴的响应式编程?”答案很简单:响应式编程会帮助你规范代码并使其更加致。你不需要考虑某些事情如何工作,也不需要考虑如何正确的使用它们。你只需以相同的方式编写代码,而不用在意你在操作什么样的数据(单事件、HTTP 调用、WebSocket 等)。每件事都是数据流,每个数据流都拥有很多函数,你可以使用这些函数来进行操作数据,比如 map,还有 filter。这些函数会返回新的可操作的流,循环往复。

 

第 3 段(可获 1.31 积分)

响应式编程让你对代码进行大量抽象。它让你可以创建交互式的用户体验,而你只需要专注于业务逻辑。

reactive-clicks
图片来自 https://gist.github.com/staltz/868e7e9bc2a7b8c1f754

JavaScript 中的响应式编程

在 JavaScript 中,我们有几个不错的库用于处理数据流。最著名的一个是 RxJS。它是 ReactiveX 的延伸,是观察流的异步编程 API。你可以创建一个 Observable (数据流),然后通过不同的函数来处理它。

 

第 4 段(可获 1.1 积分)

另一个是 Most.js。它拥有最好的性能,来自性能比较的数字能够证明这一点。

我还想提一提一个小而快的库,由 Cycle.js 的创造者为 Cycle.js 而创造。它的名称是 xstream。它只有 26 个方法,大约 30kb,是 JS 响应式编程库中最快的库之一。

我会在下面的示例中使用 xstream 库。Cycle.js 是一个小型框架,所以我想使用最小的响应式编程库来配合它。

什么是 Cycle.js?

Cycle.js 是函数式和响应式的 JavaScript 框架。它将你的应用抽象为一个纯函数,main()。在函数式编程中,函数应该只有输入和输出,没有副 作用。Cycle.js 的 main() 函数,输入是从外部世界读取影响(来源),而输出(汇出)是向外部世界写入影响。对副作用的管理是通过驱动器完成的。驱动器是用于处理 DOM 影响、HTTP 影响和 WebSocket 等影响的插件。

 

第 5 段(可获 2.19 积分)

Cycle.js
图片来自 Cycle.js 网站

Cycle.js 帮助我们构建自己的用户界面,测试它们,并编写可复用的代码。每个组件都是一个可以独立运行的纯函数。

它的核心 API 只有一个函数,run。

run(app, drivers);

它有两个参数,app 和 drivers。app 是主纯函数,drivers 是用于处理副作用的插件。

Cycle.js 将其它功能拆分成更小的模块,它们是:

第 6 段(可获 1.95 积分)

--广告--

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
第 7 段(可获 1.14 积分)

这会安装 @cycle/dom、@cycle/xstream-run、和 xstream。我们还需要 babel、browserify 和 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'"
}
第 8 段(可获 0.51 积分)

我们使用 run start 来运行 Cycle.js 应用。

就这些。我们已经完成设置,现在开始编写一些代码。先在 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 经过转译和打包而来。

第 9 段(可获 0.9 积分)

是时候写点 Cycle.js 代码了。打开 main.js 文件,引入我们需要的所有依赖:

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

我们包含了 xstreamrunmakeDOMDriver 以及可以帮助我们操作虚拟 DOM(div、button 和 p) 的函数。

现在来写 main 函数,就像这样:

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')
});
第 10 段(可获 0.54 积分)

这就是我们的 main 函数。它从参数获取 sources,返回汇出对象。sources 是 DOM 元素,而汇出对象是虚拟 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$ 看起来像这样:

 

第 11 段(可获 1.06 积分)
----(-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$ 流中的值进行累加。

最后,为了使整个循环运作,我们需要在 main 函数之后调用 run。

第 12 段(可获 1.06 积分)

最后一件事是创建虚拟 DOM,这是代码:

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

我们将 count$ 流中的数据进行映射并对流中的每一项返回一个虚拟 DOM。虚拟 DOM 包含主要的 div 包装,两个按钮和一个段落。正如你所见,Cycle.js 使用 JavaScript 函数来操作 DOM,不过 JSX 也可以实现

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

第 13 段(可获 1 积分)
return {
  DOM: vdom$,
};

我们将 main 函数和连接到 ID 为 main 的 div 的 DOM 驱动器传入,并从那个 div 获得事件流。我们关闭自己的循环,做出了完美的 Cycle.js 应用。

这是演示其运作过程:

Animated GIF demonstrating the counter being incremented and decremented

就是这样!这里介绍了如何操作 DOM 流。如果你想看看如何使用 Sycle.js 操作 HTTP 流,我写过一篇相关的文章(在博客中)[http://ivanjov.com/working-with-http-streams-with-cycle-js/]

我将所有代码推送到了 Github 库中。你可以检查并在自己的机器上运行。

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

第 14 段(可获 1.36 积分)

现在你了解了响应式编程的基础概念,也看到了一个简单的 Sycle.js 示例。我们来聊聊为什么我会在下一个项目中使用它。

在我设计 Web 应用时遇到最大的问题是处理大量的代码和各种来源的大量数据。我喜欢 React,而且已经在众多项目中使用它,但 React 并不能解决我的问题。

在涉及到渲染数据和改变应用状态的情况时,React 处理很好。实际上,整合了整套方法的组件确实对写更好、可测试和可维护的代码发挥了惊人的作用。但它还是缺少了一些东西。

 

第 15 段(可获 1.5 积分)

来看看与 React 相比,Cycle.js 的存在的一些优点和缺点。

优点

1. 大量代码

如果应用变得很大,React 会出现一些问题。想像一下,你的 100 个容器中使用了 100 个组件,而且它们各有自己的风格、功能和测试。这会有大量目录,其中包含大量的文件,而文件中又存在大量的代码行。你肯定明白我想说什么——很难在这些文件中浏览代码。

Cycle.js 则会有所帮助。它被设计来通过将项目拆分成独立组件来处理大量代码, 些组件可以完全独立,分别测试,不会有副作用。没有 Redux,没有副作用,所有东西都是纯数据流。

 

第 16 段(可获 1.43 积分)

2. 数据流

我在 React 中遇到最大的问题是数据流。React 的设计并不关心数据流,那不是 React 的核心。开发者们试图解决这个问题,所以我们有很多库和方法。最流行的就是 Redux。但它并不完美。你需要花时间来配置它,还需要编写只能处理数据流的代码。

而 Cycle.js 的创建者希望创建一个框架来专门处理数据流,因为这不应该是你要考虑的问题。你只需要写进行操作数据的函数,Cycle.js 会处理其它事情。

 

第 17 段(可获 1.58 积分)

3. 副作用

React 有处理副作用的问题。在 React 应用中没有标准的化办法来处理副作用。很有很工具中以帮助你处理这个问题,但需要花大量时间来设置和学习如何使用它们。最流行的库包括redux-sagaredux-effectsredux-side-effects 和 redux-loop。你明白我的意思吗?太多了... 你需要做出选择并在自己的代码中进行实现。

Cycle.js 不需要那些。你只要引入需要的驱动(DOM、HTTP 或其它)并使用就好。驱动会把数据送给你的纯函数,你可以改变它并把它送回驱动进行渲染或用于其它事情。最重要的是,这是标准化的。这是 Cycle.js 与生俱来的,不需要第三方库,非常简单!

 

第 18 段(可获 1.96 积分)

4. 函数式编程

最后一点并非不重要,这就是函数式编程。React 创建者声称 React 使用了函数式编程,但这并不完全是真的。它包含了大量 OOP、类,使用 this 关键字,如果使用不当会让人很头痛…Cycle.js 的中心思想就是函数式编程。所有东西都是函数,不依赖于外部状态。而且,不存在类或者类似于类的东西。这非常容易测试和维护。

缺点

1. 社区

目前 React 是最流行的框架,到处都在使用。Cycle.js 却不是。它还不是很流行,可能你会遇到一些意外的情况,却不找到代码问题的解决办法,这就麻烦了。有时候你在互联网上找不到答案,那就只能靠自己。在你进行一个非重点项目并且拥有大量空闲时间的时候,这并不是什么问题,但如果你在公司里,而项目时间又很紧迫时该怎么办?调试代码会花掉大量的时间。

 

第 19 段(可获 2.36 积分)

不过这种情况正在改变。很多开发者开始使用 Cycle.js 并谈论它的问题,以及一起解决这些问题。Cycle.js 还有很好的文档,其中不乏示例。我还没遇到过很难调试的复杂问题。

2. 学习新模式

响应式编程是一种不同寻常的模式,你需要花时间来适应它。之后,所有事情都会变得容易,但是如果你的最后期限比较紧迫,花时间来学习新东西就会是个问题。

3. 某些应用不需要响应式

第 20 段(可获 1.38 积分)

的确,某些应用不需要响应式。博客、营销网站、登录页面和其它只有少量交互性的静态网络都不需要响应式。这些应用没有实时数据需要应用处理,也没有很多表单和按钮。使用响应式框架可能会让网站变得更慢。你应该判断 Web 应用是否确实需要 Cycle.js

小结

一个理想的框架可以让你专注于创建和提供功能,而不是迫使你写死板的代码。我认为 Cycle.js 向我们展示了理想框架的可能性,它迫使我们去寻找编写代码和提供功能的更好的办法。不过记住,没有什么东西是完美的,总会有改进的余地。

 

第 21 段(可获 1.7 积分)

你想试试响应式编程,或者 Cycle.js 吗?我有没有说服你进行这样的尝试?请通过评论来让我知道你的想法!

本文由同事 Michael Wanyoike 审核。感谢所有 SitePoint 的审稿人,是你们让 SitePoint 的内容变更优秀!

 

第 22 段(可获 0.64 积分)

文章评论