学习RxJS之JavaScript框架Cycle.js


Posted in Javascript onJune 17, 2019

是什么

Cycle.js 是一个极简的JavaScript框架(核心部分加上注释125行),提供了一种函数式,响应式的人机交互接口(以下简称HCI):

函数式

Cycle.js 把应用程序抽象成一个纯函数 main(),从外部世界读取副作用(sources),然后产生输出(sinks) 传递到外部世界,在那形成副作用。这些外部世界的副作用,做为Cycle.js的插件存在(drivers),它们负责:处理DOM、提供HTTP访问等。

响应式
Cycle.js 使用 rx.js 来实现关注分离,这意味着应用程序是基于事件流的,数据流是Observable 的:

HCI

HCI 是双向的对话,人机互为观察者

在这个交互模型中,人机之间的信息流互为输出输出,构成一个循环,也即 Cycle这一命名所指,框架的Logo更是以莫比乌斯环贴切的描述了这个循环。cycle_log

唯一的疑惑会是:循环无头无尾,信息流从何处发起?好问题,答案是:

However, we need a .startWith() to give a default value. Without this, nothing would be shown! Why? Because our sinks is reacting to sources, but sources is reacting to sinks. If no one triggers the first event, nothing will happen. —— via examples

有了.startWith() 提供的这个初始值,整个流程得以启动,自此形成一个闭环,一个事件驱动的永动机 :)

Drivers

driver 是 Cycle.js 主函数 main()和外部世界打交道的接口,比如HTTP请求,比如DOM操作,这些是由具体的driver 负责的,它的存在确保了 main()的纯函数特性,所有副作用和繁琐的细节皆由 driver来实施——所以 @cycle/core 才125 行,而@cycle/dom 却有 4052 行之巨。

driver也是一个函数,从流程上来说,driver 监听sinks(main()的输出)做为输入,执行一些命令式的副作用,并产生出sources做为main()的输入。

DOM Driver

即 @cycle/dom,是使用最为频繁的driver。实际应用中,我们的main()会与DOM进行交互:

  • 需要传递内容给用户时,main()会返新的DOM sinks,以触发domDriver()生成virtual-dom,并渲染
  • main()订阅domDriver()的输出值(做为输入),并据此进行响应

组件化
每个Cycle.js应用程序不管多复杂,都遵循一套输入输出的基本法,因此,组件化是很容易实现,无非就是函数对函数的组合调用

实战

准备工作

安装全局模块

依赖模块一览

"devDependencies": {
"babel-plugin-transform-react-jsx": "^6.8.0",
"babel-preset-es2015": "^6.9.0",
"babelify": "^7.3.0",
"browserify": "^13.0.1",
"uglifyify": "^3.0.1",
"watchify": "^3.7.0"
},
"dependencies": {
"@cycle/core": "^6.0.3",
"@cycle/dom": "^9.4.0",
"@cycle/http": "^8.2.2"
}

.babelrc (插件支持JSX语法)

{
"plugins": [
["transform-react-jsx", { "pragma": "hJSX" }]
],
"presets": ["es2015"]
}

Scripts(热生成和运行服务器)

"scripts": {
"start": "http-server",
"build": "../node_modules/.bin/watchify index.js -v -g uglifyify -t babelify -o bundle.js"
}

以下实例需要运行时,可以开两个shell,一个跑热编译,一个起http-server(爱用currently亦可

交互实例1

功能:两个button,一加一减, 从0起步,回显计数
demo地址: http://output.jsbin.com/lamexacaku

HTML代码

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>components</title>
</head>
<body>
<div id="container"></div>
<script src="bundle.js"></script>
</body>
</html>

index.js

import Cycle from '@cycle/core'
import { makeDOMDriver, hJSX } from '@cycle/dom'
function main({ DOM }) {
const decrement$ = DOM.select('.decrement').events('click').map(_ => -1)
const increment$ = DOM.select('.increment').events('click').map(_ => +1)
const count$ = increment$.merge(decrement$)
.scan((x, y) => x + y)
.startWith(0)
return {
DOM: count$.map(count =>
<div>
<input type="button" className="decrement" value=" - "/>
<input type="button" className="increment" value=" + "/>
<div>
Clicked {count} times~
</div>
</div>
)
}
}
Cycle.run(main, {
DOM: makeDOMDriver('#container'),
})

不难看出:

  • main()是个纯函数,从始至终不依赖外部状态,它的所有动力来自于DOM事件源click,这个状态机依靠Observable.prototype.scan()得以计算和传递,最后生成sinks传递给DOM driver以渲染;
  • 启动了这个循环是 .startWith();
  • Cycle.run是应用程序的入口,加载main()和DOM driver,后者对一个HTML容器进行渲染输出

交互实例2

功能: 一个button一个框,输入并点button后,通过Github api搜索相关的Repo,回显总数并展示第一页Repo列表

index.js

import Cycle from '@cycle/core'
import { makeDOMDriver, hJSX } from '@cycle/dom'
import { makeHTTPDriver } from '@cycle/http'
const GITHUB_SEARCH_URL = 'https://api.github.com/search/repositories?q='
function main(responses$) {
const search$ = responses$.DOM.select('input[type="button"]')
.events('click')
.map(_ => { return { url: GITHUB_SEARCH_URL } })
const text$ = responses$.DOM.select('input[type="text"]')
.events('input')
.map(e => { return { keyword: e.target.value } })
const http$ = search$.withLatestFrom(text$, (search, text)=> search.url + text.keyword)
.map(state => { return { url: state, method: 'GET' } })
const dom$ = responses$.HTTP
.filter(res$ => res$.request.url && res$.request.url.startsWith(GITHUB_SEARCH_URL))
.mergeAll()
.map(res => JSON.parse(res.text))
.startWith({ loading: true })
.map(JSON => {
return <div>
<input type="text"/>
<input type="button" value="search"/>
<br/>
<span>
{JSON.loading ? 'Loading...' : `total: ${JSON.total_count}`}
</span>
<ol>
{
JSON.items && JSON.items.map(repo =>
<div>
<span>repo.full_name</span>
<a href={ repo.html_url }>{ repo.html_url }</a>
</div>
)
}
</ol>
</div>
}
)
return {
DOM: dom$,
HTTP: http$,
}
}
const driver = {
DOM: makeDOMDriver('#container'),
HTTP: makeHTTPDriver(),
}
Cycle.run(main, driver)

有了实例1做铺垫,这段代码也就通俗易懂了,需要提示的是:

  • Rx的Observable对象,命名上约定以$符为结束,以示区分
  • Observable.prototype.withLatestFrom()的作用是:在当前Observable对象的事件触发时(不同于 combineLatest),去合并参数的目标Observable对象的最新状态,并传递给下一级Observer
  • 以上项目完整实例,可在 /rockdragon/rx_practise/tree/master/src/web 找到

小结

寥寥数语,并不足以概括Cycle.js,比如 MVI设计模式,Driver的编写,awesome-cycle 这些进阶项,还是留给看官们自行探索吧。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
浅析js中取绝对值的2种方法
Jul 09 Javascript
Jquery的hide及toggle方法让超链接慢慢消失
Sep 06 Javascript
基于js实现投票的实例代码
Aug 04 Javascript
谈谈impress.js初步理解
Sep 09 Javascript
jQuery实现ctrl+enter(回车)提交表单
Oct 19 Javascript
深入理解bootstrap框架之第二章整体架构
Oct 09 Javascript
使用BootStrap进行轮播图的制作
Jan 06 Javascript
Bootstrap警告(Alerts)的实现方法
Mar 22 Javascript
Javascript实现数组中的元素上下移动
Apr 28 Javascript
详解Vue 开发模式下跨域问题
Jun 06 Javascript
vue实现select下拉显示隐藏功能
Sep 30 Javascript
js实现简易ATM功能
Oct 27 Javascript
javascript系统时间设置操作示例
Jun 17 #Javascript
深入学习TypeScript 、React、 Redux和Ant-Design的最佳实践
Jun 17 #Javascript
Vue程序调试的方法
Jun 17 #Javascript
Vue拖拽组件列表实现动态页面配置功能
Jun 17 #Javascript
javascript实现日历效果
Jun 17 #Javascript
如何使用Node.js爬取任意网页资源并输出PDF文件到本地
Jun 17 #Javascript
javascript使用substring实现的展开与收缩文字功能示例
Jun 17 #Javascript
You might like
PHP 多维数组排序实现代码
2009/08/05 PHP
Yii查询生成器(Query Builder)用法实例教程
2014/09/04 PHP
PHP类的声明与实例化及构造方法与析构方法详解
2016/01/26 PHP
PHP中用mysqli面向对象打开连接关闭mysql数据库的方法
2016/11/05 PHP
PHP实现通过CURL上传文件功能示例
2018/05/30 PHP
php实现微信公众平台发红包功能
2018/06/14 PHP
thinkPHP5框架导出Excel文件简单操作示例
2018/08/03 PHP
利用JS实现浏览器的title闪烁
2013/07/08 Javascript
jQuery之DOM对象和jQuery对象的转换与区别分析
2015/01/08 Javascript
js+CSS实现模拟华丽的select控件下拉菜单效果
2015/09/01 Javascript
ES6下子组件调用父组件的方法(推荐)
2018/02/23 Javascript
vue父组件异步获取数据传给子组件的方法
2018/07/26 Javascript
Vue CLI 3.x 自动部署项目至服务器的方法
2019/04/02 Javascript
微信小程序停止其他视频播放当前视频的实例代码
2019/12/25 Javascript
微信浏览器下拉黑边解决方案 wScroollFix
2020/01/21 Javascript
利用JS判断元素是否为数组的方法示例
2021/01/08 Javascript
[01:33]一分钟玩转DOTA2第三弹:DOTA2&DotA快捷操作大对比
2014/06/04 DOTA
[02:52]2014DOTA2西雅图国际邀请赛 CIS战队巡礼
2014/07/07 DOTA
Python中property函数用法实例分析
2018/06/04 Python
Tensorflow矩阵运算实例(矩阵相乘,点乘,行/列累加)
2020/02/05 Python
python扫描线填充算法详解
2020/02/19 Python
Html5游戏开发之乒乓Ping Pong游戏示例(二)
2013/01/21 HTML / CSS
纯html5+css3下拉导航菜单实现代码
2013/03/18 HTML / CSS
Veronica Beard官网:在酷、经典和别致之间找到了平衡
2018/01/11 全球购物
JoJo Maman Bébé爱尔兰官网:英国最受欢迎的精品母婴品牌
2020/12/20 全球购物
惠而浦美国官网:Whirlpool.com
2021/01/19 全球购物
护理学毕业生自荐信
2013/10/02 职场文书
初中生学习的自我评价
2013/11/14 职场文书
校长岗位职责
2013/11/26 职场文书
办公室保洁员岗位职责
2013/12/02 职场文书
气象学专业个人求职信
2014/04/22 职场文书
2014年后勤管理工作总结
2014/12/01 职场文书
学校趣味运动会开幕词
2016/03/04 职场文书
自荐信范文
2019/05/20 职场文书
Java异常体系非正常停止和分类
2022/06/14 Java/Android
Windows Server 2008配置防火墙策略详解
2022/06/28 Servers