學習RxJS:Cycle.js

来源:http://www.cnblogs.com/moye/archive/2016/06/16/learning_rxjs_part_two_cycle-js.html
-Advertisement-
Play Games

Cycle.js 是一個極簡的JavaScript框架(核心部分加上註釋125行),提供了一種函數式,響應式的人機交互介面。在這個交互模型中,人機之間的信息流互為輸出輸出,構成一個迴圈,也即 Cycle這一命名所指,框架的Logo更是以莫比烏斯環貼切的描述了這個迴圈。 ...


原文地址:http://www.moye.me/2016/06/16/learning_rxjs_part_two_cycle-js/

 

是什麼

Cycle.js 是一個極簡的JavaScript框架(核心部分加上註釋125行),提供了一種函數式,響應式的人機交互介面(以下簡稱HCI):

函數式

Cycle.js 把應用程式抽象成一個純函數 main(),從外部世界讀取副作用(sources),然後產生輸出(sinks) 傳遞到外部世界,在那形成副作用。這些外部世界的副作用,做為Cycle.js的插件存在(drivers),它們負責:處理DOM、提供HTTP訪問等。

circuit_flow

響應式

Cycle.js 使用 rx.js 來實現關註分離,這意味著應用程式是基於事件流的,數據流是 Observable 的:

observable_stream

HCI

HCI 是雙向的對話,人機互為觀察者:

HCI_anthropomorphism

在這個交互模型中,人機之間的信息流互為輸出輸出,構成一個迴圈,也即 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 監聽sinksmain()的輸出)做為輸入,執行一些命令式的副作用,並產生出sources做為main()的輸入。

DOM Driver

即 @cycle/dom,是使用最為頻繁的driver。實際應用中,我們的main()會與DOM進行交互:

  • 需要傳遞內容給用戶時,main()會返新的DOM sinks,以觸發domDriver()生成virtual-dom,並渲染
  • main()訂閱domDriver()的輸出值(做為輸入),並據此進行響應

domDriver

組件化

每個Cycle.js應用程式不管多複雜,都遵循一套輸入輸出的基本法,因此,組件化是很容易實現,無非就是函數對函數的組合調用

Composable

實戰

準備工作

安裝全局模塊

npm install -g http-server

依賴模塊一覽

"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亦可

$ npm run build
$ npm start

交互實例1

cycle_example_1

HTML代碼 (實例2同,略
<!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

cycle_example_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 這些進階項,還是留給看官們自行探索吧。

 

更多文章請移步我的blog新地址: http://www.moye.me/ 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 【正文】 面試必問關鍵詞:JVM垃圾回收、類載入機制。 先把本文的目錄畫一個思維導圖:(圖的源文件在本文末尾) 一、Java引用的四種狀態: 強引用: 用的最廣。我們平時寫代碼時,new一個Object存放在堆記憶體,然後用一個引用指向它,這就是強引用。 如果一個對象具有強引用,那垃圾回收器絕不會回收 ...
  • PHP 全局變數 PHP中預定義了幾個超級全局變數(superglobals) ,這意味著它們在一個腳本的全部作用域中都可用。 你不需要特別說明,就可以在函數及類中使用。 PHP 超級全局變數列表: $GLOBALS $_SERVER $_REQUEST $_POST $_GET $_FILES $ ...
  • 在scala里,類繼承有兩點限制: 重寫方法需要使用override關鍵字; 只有主構造函數才能往父類構造函數中傳參數。 在java1.5中引入了override註解,但不強制使用。不過在scala中要想重寫方法必須使用override關鍵字。如果確實重寫了父類的方法又不使用override關鍵字的... ...
  • 最早見過手寫的,類似於下麵這種: 輸出如下: 另外一種方法是使用timeit模塊,使用方法如下: 還可以在命令行上使用這種timeit模塊,如下: 註意:timeit模塊會多次運行程式以獲得更精確的時間,所以需要避免重覆執行帶來的影響。比方說x.sort()這種操作,因為第一次執行之後,後邊已經是排 ...
  • 我想學ruby以後開髮網站,但ruby是高級語言,隱藏了許多底層的東西,因此先熟悉c語言 首先c程式的文件名是以.c結尾的 c程式的格式: 第一行#include<stdio.h> #是一個預處理標準,用來對文本進行預處理操作,表示該行代碼要最先進行處理,在編譯代碼之前運行 include是一個指令 ...
  • 以下內容轉自博客:http://blog.chinaunix.net/uid-22670933-id-1771613.html。 一、字元編碼是怎麼回事 0. 概念 位元組是電腦的最基本存儲單位,一個位元組包括8個位. 字元是一種文字的基本單位,比如'A' 是一個字元,'漢' 也是一個字元. 1. 計 ...
  • zookeeper 單機安裝配置 1、安裝前準備 linux系統(此文環境為Centos6.5) Zookeeper安裝包,官網https://zookeeper.apache.org/,演示版本zookeeper-3.4.7.tar.gz linux系統(此文環境為Centos6.5) Zooke ...
  • 問題1:Could not calculate build plan: Plugin org.apache... 不能成功創建maven項目 解決方法1: 問題2: 轉Maven project是生成的pom.xml文件錯誤——Unknown packaging:apk以及Failed to col ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...