CYCLE.JS: FUNCTIONAL AND
REACTIVE
EUGENE ZHARKOV
Cycle.js
virtual-dom
RxJS
cycle/corecycle/dom
EXISTED APPLICATION DATA FLOW | FLUX / REDUX
VIEW
DISPATCHER
STORE
action
action
action
STORE
USER
ACTION
EXISTED APPLICATION DATA FLOW | MODEL VIEW INTENT
VIEW
INTENT
MODEL
USER
ACTION
observable
observable
observable
1
A
B
2
3
C
I’m loving it
RX UNDER THE HOOD
CODE EXAMPLE
function intent(DOM, name = '') {
const removeClicks$ = DOM.select('.remove-btn').events('click');
const stop$ = removeClicks$;
const remove$ = removeClicks$.delay(500);
return {stop$, remove$};
}
function model(actions, givenColor$) {
const x$ = Observable.interval(50).startWith(0).takeUntil(actions.stop$);
const y$ = Observable.interval(100).startWith(0).takeUntil(actions.stop$);
const color$ = Observable.merge(
givenColor$.takeUntil(actions.stop$),
actions.stop$.map(() => '#FF0000')
);
return combineLatestObj({x$, y$, color$});
}
function view(state$) {
return state$.map(({color, x, y}) => {
const style = {color, backgroundColor: '#ECECEC'};
return div('.ticker', {style}, [
h4(`x${x} ${color}`),
h1(`Y${y} ${color}`),
button('.remove-btn', 'Remove')
]);
});
}
BUTTON CLICK
HTML GENERATION
COLOR CHANGE
JSBIN.COM/VUGARUR/
BASIC EXAMPLE
import Cycle from '@cycle/core';
import {div, label, input, hr, h1, makeDOMDriver} from '@cycle/dom';
function main(sources) {
return {
DOM: sources.DOM.select('.myinput').events('input')
.map(ev => ev.target.value)
.startWith('')
.map(name =>
div([
label('Name:'),
input('.myinput', {attributes: {type: 'text'}}),
hr(),
h1(`Hello ${name}`)
])
)
};
}
Cycle.run(main, {
DOM: makeDOMDriver('#main-container')
});
JSBIN.COM/VUGARUR/
BASIC EXAMPLE
import Cycle from '@cycle/core';
import {div, label, input, hr, h1, makeDOMDriver} from '@cycle/dom';
function main(sources) {
return {
DOM: sources.DOM.select('.myinput').events('input')
.map(ev => ev.target.value)
.startWith('')
.map(name =>
div([
label('Name:'),
input('.myinput', {attributes: {type: 'text'}}),
hr(),
h1(`Hello ${name}`)
])
)
};
}
Cycle.run(main, {
DOM: makeDOMDriver('#main-container')
});
HANDLE EVENT
GET INPUT VALUE
DEFAULT VALUE
HTML
JSBIN.COM/VUGARUR/
BASIC EXAMPLE
import Cycle from '@cycle/core';
import {div, label, input, hr, h1, makeDOMDriver} from '@cycle/dom';
function main(sources) {
return {
DOM: sources.DOM.select('.myinput').events('input')
.map(ev => ev.target.value)
.startWith('')
.map(name =>
div([
label('Name:'),
input('.myinput', {attributes: {type: 'text'}}),
hr(),
h1(`Hello ${name}`)
])
)
};
}
Cycle.run(main, {
DOM: makeDOMDriver('#main-container')
});
DOM..DRIVER..???
TEXT
DRIVER
▸ DRIVER - effect, “action”
▸ HTTP, WebSocket, User Event, Browser Event etc
▸ SINK - Observable input
JSBIN.COM/LELUHO
SINK
function main () {
return Rx.Observable.timer(0, 1000)
.map(i => `Seconds elapsed ${i}`);
}
function htmlEffect(text$) {
text$.subscribe(text => {
const container = document.querySelector('#main');
container.textContent = text;
})
}
function consoleEffect(msg$) {
msg$.subscribe(msg => console.log(msg));
}
const sink = main();
htmlEffect(sink);
consoleEffect(sink);
STREAM GOT INPUT
STEP BY STEP EFFECT
TEXT
SINK
TEXT
RX PLAYGROUD
HTTP://RXMARBLES.COM
JSBIN.COM/VUGARUR/
BASIC EXAMPLE
.map(name =>
div([
label('Name:'),
input('.myinput', {attributes: {type: 'text'}}),
hr(),
h1(`Hello ${name}`)
])
)
};
}
Cycle.run(main, {
DOM: ………..
});
CYCLE RUN
GITHUB.COM/CYCLEJS/CORE/BLOB/MASTER/SRC/CYCLE.JS
CYCLE.RUN
function run(main, drivers) {
if (typeof main !== `function`) {
throw new Error(`First argument given to Cycle.run() must be the 'main' ` +
`function.`) }
if (typeof drivers !== `object` || drivers === null) {
throw new Error(`Second argument given to Cycle.run() must be an object ` +
`with driver functions as properties.`) }
if (Object.keys(drivers).length === 0) {
throw new Error(`Second argument given to Cycle.run() must be an object ` +
`with at least one driver function declared as a property.`) }
let sinkProxies = makeSinkProxies(drivers)
let sources = callDrivers(drivers, sinkProxies)
let sinks = main(sources)
let subscription = replicateMany(sinks, sinkProxies).subscribe()
let sinksWithDispose = attachDisposeToSinks(sinks, subscription)
let sourcesWithDispose = attachDisposeToSources(sources)
return {sources: sourcesWithDispose, sinks: sinksWithDispose}
}
BASIC DEMO
HTTP DEMO
JSX MIX DEMO
THANK YOU
@2J2E
EU.ZHARKOV@GMAIL.COM
!
TEXT
RESOURCES
▸ https://jsbin.com/zizabe/
▸ https://jsbin.com/xataxe/
▸ https://jsbin.com/vugarur
▸ https://jsbin.com/dogumi
▸ egghead.io

Cycle.js: Functional and Reactive

  • 1.
  • 2.
  • 3.
    EXISTED APPLICATION DATAFLOW | FLUX / REDUX VIEW DISPATCHER STORE action action action STORE USER ACTION
  • 4.
    EXISTED APPLICATION DATAFLOW | MODEL VIEW INTENT VIEW INTENT MODEL USER ACTION observable observable observable
  • 5.
  • 6.
  • 7.
    CODE EXAMPLE function intent(DOM,name = '') { const removeClicks$ = DOM.select('.remove-btn').events('click'); const stop$ = removeClicks$; const remove$ = removeClicks$.delay(500); return {stop$, remove$}; } function model(actions, givenColor$) { const x$ = Observable.interval(50).startWith(0).takeUntil(actions.stop$); const y$ = Observable.interval(100).startWith(0).takeUntil(actions.stop$); const color$ = Observable.merge( givenColor$.takeUntil(actions.stop$), actions.stop$.map(() => '#FF0000') ); return combineLatestObj({x$, y$, color$}); } function view(state$) { return state$.map(({color, x, y}) => { const style = {color, backgroundColor: '#ECECEC'}; return div('.ticker', {style}, [ h4(`x${x} ${color}`), h1(`Y${y} ${color}`), button('.remove-btn', 'Remove') ]); }); } BUTTON CLICK HTML GENERATION COLOR CHANGE
  • 8.
    JSBIN.COM/VUGARUR/ BASIC EXAMPLE import Cyclefrom '@cycle/core'; import {div, label, input, hr, h1, makeDOMDriver} from '@cycle/dom'; function main(sources) { return { DOM: sources.DOM.select('.myinput').events('input') .map(ev => ev.target.value) .startWith('') .map(name => div([ label('Name:'), input('.myinput', {attributes: {type: 'text'}}), hr(), h1(`Hello ${name}`) ]) ) }; } Cycle.run(main, { DOM: makeDOMDriver('#main-container') });
  • 9.
    JSBIN.COM/VUGARUR/ BASIC EXAMPLE import Cyclefrom '@cycle/core'; import {div, label, input, hr, h1, makeDOMDriver} from '@cycle/dom'; function main(sources) { return { DOM: sources.DOM.select('.myinput').events('input') .map(ev => ev.target.value) .startWith('') .map(name => div([ label('Name:'), input('.myinput', {attributes: {type: 'text'}}), hr(), h1(`Hello ${name}`) ]) ) }; } Cycle.run(main, { DOM: makeDOMDriver('#main-container') }); HANDLE EVENT GET INPUT VALUE DEFAULT VALUE HTML
  • 10.
    JSBIN.COM/VUGARUR/ BASIC EXAMPLE import Cyclefrom '@cycle/core'; import {div, label, input, hr, h1, makeDOMDriver} from '@cycle/dom'; function main(sources) { return { DOM: sources.DOM.select('.myinput').events('input') .map(ev => ev.target.value) .startWith('') .map(name => div([ label('Name:'), input('.myinput', {attributes: {type: 'text'}}), hr(), h1(`Hello ${name}`) ]) ) }; } Cycle.run(main, { DOM: makeDOMDriver('#main-container') }); DOM..DRIVER..???
  • 11.
    TEXT DRIVER ▸ DRIVER -effect, “action” ▸ HTTP, WebSocket, User Event, Browser Event etc ▸ SINK - Observable input
  • 12.
    JSBIN.COM/LELUHO SINK function main (){ return Rx.Observable.timer(0, 1000) .map(i => `Seconds elapsed ${i}`); } function htmlEffect(text$) { text$.subscribe(text => { const container = document.querySelector('#main'); container.textContent = text; }) } function consoleEffect(msg$) { msg$.subscribe(msg => console.log(msg)); } const sink = main(); htmlEffect(sink); consoleEffect(sink); STREAM GOT INPUT STEP BY STEP EFFECT
  • 13.
  • 14.
  • 15.
    JSBIN.COM/VUGARUR/ BASIC EXAMPLE .map(name => div([ label('Name:'), input('.myinput',{attributes: {type: 'text'}}), hr(), h1(`Hello ${name}`) ]) ) }; } Cycle.run(main, { DOM: ……….. }); CYCLE RUN
  • 16.
    GITHUB.COM/CYCLEJS/CORE/BLOB/MASTER/SRC/CYCLE.JS CYCLE.RUN function run(main, drivers){ if (typeof main !== `function`) { throw new Error(`First argument given to Cycle.run() must be the 'main' ` + `function.`) } if (typeof drivers !== `object` || drivers === null) { throw new Error(`Second argument given to Cycle.run() must be an object ` + `with driver functions as properties.`) } if (Object.keys(drivers).length === 0) { throw new Error(`Second argument given to Cycle.run() must be an object ` + `with at least one driver function declared as a property.`) } let sinkProxies = makeSinkProxies(drivers) let sources = callDrivers(drivers, sinkProxies) let sinks = main(sources) let subscription = replicateMany(sinks, sinkProxies).subscribe() let sinksWithDispose = attachDisposeToSinks(sinks, subscription) let sourcesWithDispose = attachDisposeToSources(sources) return {sources: sourcesWithDispose, sinks: sinksWithDispose} }
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
    TEXT RESOURCES ▸ https://jsbin.com/zizabe/ ▸ https://jsbin.com/xataxe/ ▸https://jsbin.com/vugarur ▸ https://jsbin.com/dogumi ▸ egghead.io