Learn the JavaScript framework of rxjs cycle.js


What is it?

Cycle. JS is a minimalist JavaScript framework (core with 125 lines of comments) that provides a functional, responsive human-computer interface (HCI):

Function formula

Cycle. JS abstracts the application as a pure function main (), reads side effects from the outside world, and then generates sinks that are passed to the outside world, where they form side effects. These side effects of the external world, as drivers of Cycle. JS plug-ins, are responsible for handling DOM, providing HTTP access, and so on.

Response type
Cycle.js uses rx.js to achieve separation of concerns, which means that the application is event-based and the data stream is Observable:


HCI is a two-way dialogue and human-computer interaction is the observer

In this interactive model, the information flow between human and computer is output to each other, forming a cycle, that is, Cycle, which means that the logo of the framework describes this cycle with Mobius ring. Cycle_log

The only doubt is: endless circulation, where does the information flow originate? Good question, the answer is:

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

With the initial value provided by. startWith (), the whole process can be started and a closed loop, an event-driven perpetual machine, is formed thereafter.


Driver is the interface between Cycle.js main function () and the outside world, such as HTTP requests, such as DOM operations, which are responsible for specific drivers. Its existence ensures the pure functional nature of main (), and all side effects and tedious details are implemented by drivers – so @cycle/core is 125 lines, while @cycle/dom is 4052 lines.

Driver is also a function. Procedurally, the driver listens for the output of sinks (main () as input, performs some imperative side effects, and produces sources as input of main ().

DOM Driver

That is @cycle/dom, which is the most frequently used driver. In practice, our main () interacts with DOM:

  • When content needs to be delivered to the user, main () returns a new DOM sinks to trigger domDriver () to generate virtual-dom and render it.
  • Main () subscribes to the output value of domDriver () as input and responds accordingly

Every Cycle.js application, no matter how complex, follows a set of basic methods of input and output. Therefore, componentization is easy to implement. It is nothing more than a combination call of functions.

actual combat


Install global module

List of Dependent Modules

"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 (plug-ins support JSX syntax)

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

Scripts (Hot Generation and Running Server)

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

The following example needs to run. It can open two shells, one for hot compilation and one for http-server.

Interaction instance 1

Function: Two buttons, one plus one minus, starting from 0, echo counting
Demo address: http://output.jsbin.com/lamexacaku

HTML code

<!DOCTYPE html>
<meta charset="utf-8">
<script src="bundle.js"></script>


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)
return {
DOM: count$.map(count =>
<input type="button" className="decrement" value=" - "/>
<input type="button" className="increment" value=" + "/>
Clicked {count} times~
Cycle.run(main, {
DOM: makeDOMDriver('#container'),

It is not difficult to see that:

  • Main () is a pure function, which does not depend on external state from beginning to end. All its power comes from the DOM event source click. This state machine is computed and transmitted by Observable. prototype. scan (), and finally generates sinks which are passed to DOM driver for rendering.
  • StartWith ();
  • Cycle.run is the entry of the application, loading main () and DOM driver, which render the output of a HTML container.

Interaction instance 2

Function: A button and a box. After entering and clicking button, search the relevant Repo through Github api, echo the total number and display the first page of the Repo list.


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"]')
.map(_ => { return { url: GITHUB_SEARCH_URL } })
const text$ = responses$.DOM.select('input[type="text"]')
.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))
.map(res => JSON.parse(res.text))
.startWith({ loading: true })
.map(JSON => {
return <div>
<input type="text"/>
<input type="button" value="search"/>
{JSON.loading ? 'Loading...' : `total: ${JSON.total_count}`}
JSON.items && JSON.items.map(repo =>
<a href={ repo.html_url }>{ repo.html_url }</a>
return {
DOM: dom$,
HTTP: http$,
const driver = {
DOM: makeDOMDriver('#container'),
HTTP: makeHTTPDriver(),
Cycle.run(main, driver)

With Example 1 as the foundation, this code will be easy to understand, need to be prompted that:

  • Rx’s Observable object, which ends with a $character in its naming convention, is distinguished
  • The function of Observable. prototype. withLatestFrom () is to merge the latest state of the target Observable object of the parameter and pass it to the next level Observer when the event of the current Observable object triggers (unlike combineLatest).
  • A complete example of the above project can be found in / rockdragon / rx_practise / tree / Master / SRC / Web


Few words, not enough to summarize Cycle. js, such as MVI design patterns, Driver writing, awesome-cycle, these advanced items, or left to the viewers to explore.

The above is the whole content of this article. I hope it will be helpful to everyone’s study, and I hope you will support developpaer more.