學(xué)習(xí)RxJS之JavaScript框架Cycle.js
是什么
Cycle.js 是一個(gè)極簡的JavaScript框架(核心部分加上注釋125行),提供了一種函數(shù)式,響應(yīng)式的人機(jī)交互接口(以下簡稱HCI):
函數(shù)式
Cycle.js 把應(yīng)用程序抽象成一個(gè)純函數(shù) main(),從外部世界讀取副作用(sources),然后產(chǎn)生輸出(sinks) 傳遞到外部世界,在那形成副作用。這些外部世界的副作用,做為Cycle.js的插件存在(drivers),它們負(fù)責(zé):處理DOM、提供HTTP訪問等。
響應(yīng)式
Cycle.js 使用 rx.js 來實(shí)現(xiàn)關(guān)注分離,這意味著應(yīng)用程序是基于事件流的,數(shù)據(jù)流是Observable 的:
HCI
HCI 是雙向的對話,人機(jī)互為觀察者
在這個(gè)交互模型中,人機(jī)之間的信息流互為輸出輸出,構(gòu)成一個(gè)循環(huán),也即 Cycle這一命名所指,框架的Logo更是以莫比烏斯環(huán)貼切的描述了這個(gè)循環(huán)。cycle_log
唯一的疑惑會(huì)是:循環(huán)無頭無尾,信息流從何處發(fā)起?好問題,答案是:
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() 提供的這個(gè)初始值,整個(gè)流程得以啟動(dòng),自此形成一個(gè)閉環(huán),一個(gè)事件驅(qū)動(dòng)的永動(dòng)機(jī) :)
Drivers
driver 是 Cycle.js 主函數(shù) main()和外部世界打交道的接口,比如HTTP請求,比如DOM操作,這些是由具體的driver 負(fù)責(zé)的,它的存在確保了 main()的純函數(shù)特性,所有副作用和繁瑣的細(xì)節(jié)皆由 driver來實(shí)施——所以 @cycle/core 才125 行,而@cycle/dom 卻有 4052 行之巨。
driver也是一個(gè)函數(shù),從流程上來說,driver 監(jiān)聽sinks(main()的輸出)做為輸入,執(zhí)行一些命令式的副作用,并產(chǎn)生出sources做為main()的輸入。
DOM Driver
即 @cycle/dom,是使用最為頻繁的driver。實(shí)際應(yīng)用中,我們的main()會(huì)與DOM進(jìn)行交互:
- 需要傳遞內(nèi)容給用戶時(shí),main()會(huì)返新的DOM sinks,以觸發(fā)domDriver()生成virtual-dom,并渲染
- main()訂閱domDriver()的輸出值(做為輸入),并據(jù)此進(jìn)行響應(yīng)
組件化
每個(gè)Cycle.js應(yīng)用程序不管多復(fù)雜,都遵循一套輸入輸出的基本法,因此,組件化是很容易實(shí)現(xiàn),無非就是函數(shù)對函數(shù)的組合調(diào)用
實(shí)戰(zhàn)
準(zhǔn)備工作
安裝全局模塊
依賴模塊一覽
"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(熱生成和運(yùn)行服務(wù)器)
"scripts": {
"start": "http-server",
"build": "../node_modules/.bin/watchify index.js -v -g uglifyify -t babelify -o bundle.js"
}
以下實(shí)例需要運(yùn)行時(shí),可以開兩個(gè)shell,一個(gè)跑熱編譯,一個(gè)起http-server(愛用currently亦可
交互實(shí)例1
功能:兩個(gè)button,一加一減, 從0起步,回顯計(jì)數(shù)
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()是個(gè)純函數(shù),從始至終不依賴外部狀態(tài),它的所有動(dòng)力來自于DOM事件源click,這個(gè)狀態(tài)機(jī)依靠Observable.prototype.scan()得以計(jì)算和傳遞,最后生成sinks傳遞給DOM driver以渲染;
- 啟動(dòng)了這個(gè)循環(huán)是 .startWith();
- Cycle.run是應(yīng)用程序的入口,加載main()和DOM driver,后者對一個(gè)HTML容器進(jìn)行渲染輸出
交互實(shí)例2
功能: 一個(gè)button一個(gè)框,輸入并點(diǎn)button后,通過Github api搜索相關(guān)的Repo,回顯總數(shù)并展示第一頁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)
有了實(shí)例1做鋪墊,這段代碼也就通俗易懂了,需要提示的是:
- Rx的Observable對象,命名上約定以$符為結(jié)束,以示區(qū)分
- Observable.prototype.withLatestFrom()的作用是:在當(dāng)前Observable對象的事件觸發(fā)時(shí)(不同于 combineLatest),去合并參數(shù)的目標(biāo)Observable對象的最新狀態(tài),并傳遞給下一級Observer
- 以上項(xiàng)目完整實(shí)例,可在 /rockdragon/rx_practise/tree/master/src/web 找到
小結(jié)
寥寥數(shù)語,并不足以概括Cycle.js,比如 MVI設(shè)計(jì)模式,Driver的編寫,awesome-cycle 這些進(jìn)階項(xiàng),還是留給看官們自行探索吧。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JavaScript的for循環(huán)中嵌套一個(gè)點(diǎn)擊事件的問題解決
本文主要介紹了JavaScript的for循環(huán)中嵌套一個(gè)點(diǎn)擊事件點(diǎn)擊一次彈出多個(gè)相同的值的解決方法,具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-03-03
值得分享的JavaScript實(shí)現(xiàn)圖片輪播組件
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)圖片輪播組件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
JS樹形結(jié)構(gòu)根據(jù)id獲取父級節(jié)點(diǎn)元素的示例代碼
這篇文章主要介紹了JS樹形結(jié)構(gòu)根據(jù)id獲取父級節(jié)點(diǎn)元素,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05
JavaScript+H5實(shí)現(xiàn)微信搖一搖功能
這篇文章主要為大家詳細(xì)介紹了JavaScript+H5實(shí)現(xiàn)微信搖一搖功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
bootstrap模態(tài)框關(guān)閉后清除模態(tài)框的數(shù)據(jù)方法
今天小編就為大家分享一篇bootstrap模態(tài)框關(guān)閉后清除模態(tài)框的數(shù)據(jù)方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
js 模仿錨點(diǎn)定位的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄猨s 模仿錨點(diǎn)定位的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11
bootstrap-table實(shí)現(xiàn)表頭固定以及列固定的方法示例
這篇文章主要介紹了bootstrap-table實(shí)現(xiàn)表頭固定以及列固定的方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
理運(yùn)用命名空間讓js不產(chǎn)生沖突避免全局變量的泛濫
為了避免變量之間的覆蓋與沖突,可以生成命名空間,命名空間是一種特殊的前綴,在不同的匿名函數(shù)中,根據(jù)功能聲明一個(gè)不同的命名空間2014-06-06
TypeScript實(shí)現(xiàn)數(shù)組和樹的相互轉(zhuǎn)換
樹或者圖是個(gè)比較抽象的概念,并不存在這樣的數(shù)據(jù)類型。數(shù)組就比較簡單了,因此數(shù)組和樹的轉(zhuǎn)換可以理解為數(shù)組和對象之間的轉(zhuǎn)換。本文將用TypeScript實(shí)現(xiàn)數(shù)組和樹的相互轉(zhuǎn)換,感興趣的可以了解一下2022-06-06

