rxjs + redux + react = amazing

98
RxJS + Redux + React = Amazing Jay Phelps | @_jayphelps Side Effect Management with RxJS

Upload: jay-phelps

Post on 16-Apr-2017

6.762 views

Category:

Software


7 download

TRANSCRIPT

Page 1: RxJS + Redux + React = Amazing

RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Side Effect Management with RxJS

Page 2: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Managing state stuff is hard

Page 3: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Redux makes it simple (not necessarily easy)

Page 4: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Managing async stuff is harder

Page 5: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Some async is complex regardless of the abstraction

Page 6: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

RxJS makes it manageable

Page 7: RxJS + Redux + React = Amazing

Jay PhelpsSenior Software Engineer |

@_jayphelps

Page 8: RxJS + Redux + React = Amazing

What is redux?

Jay Phelps | @_jayphelps

Page 9: RxJS + Redux + React = Amazing

Crash Course

Jay Phelps | @_jayphelps

Page 10: RxJS + Redux + React = Amazing

What is redux?

Jay Phelps | @_jayphelps

Provides predicable state management using actions and reducers

Page 11: RxJS + Redux + React = Amazing

What's an "action"?

Jay Phelps | @_jayphelps

Describes something has (or should) happen, but they don't specify how it should be done

Page 12: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

{type:'CREATE_TODO',payload:'BuildmyfirstReduxapp'}

Page 13: RxJS + Redux + React = Amazing

What's an "reducer"?

Jay Phelps | @_jayphelps

A pure function that takes the previous state and an action and returns the new state

Page 14: RxJS + Redux + React = Amazing

What's an "reducer"?

Jay Phelps | @_jayphelps

Sometimes it returns the previous state

(state,action)=>state

Page 15: RxJS + Redux + React = Amazing

What's an "reducer"?

Jay Phelps | @_jayphelps

Sometimes it computes new state

(state,action)=>state+action.payload

Page 16: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

constcounter=(state=0,action)=>{switch(action.type){case'INCREMENT':returnstate+1;

case'DECREMENT':returnstate-1;default:returnstate;}};

Page 17: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Reducers handle state transitions, but they must be done synchronously.

Page 18: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

constcounter=(state=0,action)=>{switch(action.type){case'INCREMENT':returnstate+1;

case'DECREMENT':returnstate-1;default:returnstate;}};

Page 19: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

What are async stuff do we commonly do?

Page 20: RxJS + Redux + React = Amazing

Async

Jay Phelps | @_jayphelps

• User interactions (mouse, keyboard, etc) • AJAX • Timers/Animations • Web Sockets • Work Workers, etc

Page 21: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Some can be handled synchronously

Page 22: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

<buttononClick={()=>dispatch({type:'INCREMENT'})}>Increment</button>

Page 23: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

constcounter=(state=0,action)=>{switch(action.type){case'INCREMENT':returnstate+1;

case'DECREMENT':returnstate-1;default:returnstate;}};

Page 24: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Sometimes you need more control

Page 25: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

• AJAX cancellation/composing • Debounce/throttle/buffer/etc • Drag and Drop • Web Sockets, Work Workers, etc

Page 26: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Use middleware to manage async / side effects

Page 27: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Most of them use callbacks or Promises

Page 28: RxJS + Redux + React = Amazing

Callbacks

Jay Phelps | @_jayphelps

The most primitive way to handle async in JavaScript

Page 29: RxJS + Redux + React = Amazing

Callbacks

Jay Phelps | @_jayphelps

fetchSomeData((error,data)=>{if(!error){dispatch({type:'HERES_THE_DATA',data});}});

Page 30: RxJS + Redux + React = Amazing

Callback Hell

Jay Phelps | @_jayphelps

fetchSomeData(id,(error,data)=>{if(!error){dispatch({type:'HERES_THE_FIRST_CALL_DATA',data});

fetchSomeData(data.parentId,(error,data)=>{if(!error){dispatch({type:'HERES_SOME_MORE',data});fetchSomeData(data.parentId,(error,data)=>{if(!error){dispatch({type:'OMG_MAKE_IT_STOP',data});}});}});}});

Page 31: RxJS + Redux + React = Amazing

Promises

Jay Phelps | @_jayphelps

fetchSomeData(id).then(data=>{dispatch({type:'HERES_THE_FIRST_CALL_DATA',data});returnfetchSomeData(data.parentId);}).then(data=>{dispatch({type:'HERES_SOME_MORE',data});returnfetchSomeData(data.parentId);}).then(data=>{dispatch({type:'OKAY_IM_DONE',data});});

Page 32: RxJS + Redux + React = Amazing

Promises

Jay Phelps | @_jayphelps

• Guaranteed future • Immutable • Single value • Caching

Page 33: RxJS + Redux + React = Amazing

Promises

Jay Phelps | @_jayphelps

• Guaranteed future • Immutable • Single value • Caching

Page 34: RxJS + Redux + React = Amazing

Promises

Jay Phelps | @_jayphelps

• Guaranteed future • Immutable • Single value • Caching

Page 35: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Promises cannot be cancelled

Page 36: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Why would you want to cancel?

Page 37: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

• Changing routes/views • Auto-complete • User wants you to

Page 38: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

• Changing routes/views • Auto-complete • User wants you to

Page 39: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Daredevil

Page 40: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Daredevil

TheGetDown

Here’sDaredevil!

Page 41: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Daredevil

Page 42: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Daredevil

TheGetDown

Page 43: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Cancelling is common and often overlooked

Page 44: RxJS + Redux + React = Amazing

Promises

Jay Phelps | @_jayphelps

• Guaranteed future • Immutable • Single value • Caching

Page 45: RxJS + Redux + React = Amazing

Only AJAX is single value

Jay Phelps | @_jayphelps

• User interactions (mouse, keyboard, etc • AJAX • Animations • WebSockets, Workers, etc

Page 46: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

What do we use?

Page 47: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Observables

Page 48: RxJS + Redux + React = Amazing

Observables

Jay Phelps | @_jayphelps

• Stream of zero, one, or more values • Over any amount of time • Cancellable

Page 49: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Streams are a set, with a dimension of time

Page 50: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Being standardized for ECMAScript aka JavaScript

Page 51: RxJS + Redux + React = Amazing

RxJS

Jay Phelps | @_jayphelps

“lodash for async” - Ben Lesh

Page 52: RxJS + Redux + React = Amazing

Crash Course

Jay Phelps | @_jayphelps

Page 53: RxJS + Redux + React = Amazing

Creating Observables

Jay Phelps | @_jayphelps

• of('hello')• from([1,2,3,4])• interval(1000)• ajax('http://example.com')• webSocket('ws://echo.websocket.com')• fromEvent(button,‘click')• many more…

Page 54: RxJS + Redux + React = Amazing

Subscribing

Jay Phelps | @_jayphelps

myObservable.subscribe(value=>console.log('next',value));

Page 55: RxJS + Redux + React = Amazing

Subscribing

Jay Phelps | @_jayphelps

myObservable.subscribe(value=>console.log('next',value),err=>console.error('error',err));

Page 56: RxJS + Redux + React = Amazing

Subscribing

Jay Phelps | @_jayphelps

myObservable.subscribe(value=>console.log('next',value),err=>console.error('error',err),()=>console.info('complete!'));

Page 57: RxJS + Redux + React = Amazing

Observables can be transformed

Jay Phelps | @_jayphelps

map, filter, reduce

Page 58: RxJS + Redux + React = Amazing

Observables can be combined

Jay Phelps | @_jayphelps

concat, merge, zip

Page 59: RxJS + Redux + React = Amazing

Observables represent time

Jay Phelps | @_jayphelps

debounce, throttle, buffer, combineLatest

Page 60: RxJS + Redux + React = Amazing

Observables are lazy

Jay Phelps | @_jayphelps

retry, repeat

Page 61: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Observables can represent just about anything

Page 62: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Let’s combine RxJS and Redux!

Page 63: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Page 64: RxJS + Redux + React = Amazing
Page 65: RxJS + Redux + React = Amazing

Side effect management for redux, using Epics

Page 66: RxJS + Redux + React = Amazing

What is an Epic?

Jay Phelps | @_jayphelps

A function that takes a stream of all actions dispatched and returns a stream of new actions to dispatch

Page 67: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

“actions in, actions out”

Page 68: RxJS + Redux + React = Amazing

//Thisispseudocode,notrealfunctionpingPong(action,store){if(action.type==='PING'){return{type:'PONG'};}}

Jay Phelps | @_jayphelps

Sort of like this

Page 69: RxJS + Redux + React = Amazing

functionpingPongEpic(action$,store){returnaction$.ofType('PING').map(action=>({type:'PONG'}));}

An Epic

Jay Phelps | @_jayphelps

Page 70: RxJS + Redux + React = Amazing

constpingPongEpic=(action$,store)=>action$.ofType('PING').map(action=>({type:'PONG'}));

An Epic

Jay Phelps | @_jayphelps

Page 71: RxJS + Redux + React = Amazing

An Epic

Jay Phelps | @_jayphelps

constpingPongEpic=(action$,store)=>action$.ofType('PING').delay(1000)//<—that'sit.map(action=>({type:'PONG'}));

Page 72: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

constisPinging=(state=false,action)=>{switch(action.type){case'PING':returntrue;

case'PONG':returnfalse;

default:returnstate;}};

Page 73: RxJS + Redux + React = Amazing

constpingPongEpic=(action$,store)=>action$.ofType('PING').delay(1000).map(action=>({type:'PONG'}));

Jay Phelps | @_jayphelps

Page 74: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Debounced increment / decrement button

Page 75: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

constcounter=(state=0,action)=>{switch(action.type){case'INCREMENT':returnstate+1;

case'DECREMENT':returnstate-1;default:returnstate;}};

Page 76: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

constincrementEpic=(action$,store)=>action$.ofType('INCREMENT_DEBOUNCED').debounceTime(1000).map(()=>({type:'INCREMENT'}));

constdecrementEpic=(action$,store)=>action$.ofType('DECREMENT_DEBOUNCED').debounceTime(1000).map(()=>({type:'DECREMENT'}));

Page 77: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

constincrementEpic=(action$,store)=>action$.ofType('INCREMENT_DEBOUNCED').debounceTime(1000).map(()=>({type:'INCREMENT'}));

constdecrementEpic=(action$,store)=>action$.ofType('DECREMENT_DEBOUNCED').debounceTime(1000).map(()=>({type:'DECREMENT'}));

Page 78: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Those are contrived examples, obviously

Page 79: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Warning: non-trivial examples ahead, don’t struggle to read them entirely

Page 80: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Auto-complete

Page 81: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

onKeyUp(e){const{store}=this.props;const{value}=e.target.value;

if(this.queryId){clearTimeout(this.queryId);}

this.queryId=setTimeout(()=>{if(this.xhr){this.xhr.abort();}

constxhr=this.xhr=newXMLHttpRequest();xhr.open('GET','https://api.github.com/search/users?q='+value);xhr.onload=()=>{if(xhr.status===200){store.dispatch({type:'QUERY_FULFILLED',payload:JSON.parse(xhr.response).items});}else{store.dispatch({type:'QUERY_REJECTED',error:true,payload:{message:xhr.response,status:xhr.status}});}};xhr.send();},500);}

Plain JS

Page 82: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Epic

constautoCompleteEpic=(action$,store)=>action$.ofType('QUERY').debounceTime(500).switchMap(action=>ajax('https://api.github.com/search/users?q='+value).map(payload=>({type:'QUERY_FULFILLED',payload})));

Page 83: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

EpicconstautoCompleteEpic=(action$,store)=>action$.ofType('QUERY').debounceTime(500).switchMap(action=>ajax('https://api.github.com/search/users?q='+value).map(payload=>({type:'QUERY_FULFILLED',payload})).catch(payload=>[{type:'QUERY_REJECTED',error:true,payload}]));

Page 84: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

EpicconstautoCompleteEpic=(action$,store)=>action$.ofType('QUERY').debounceTime(500).switchMap(action=>ajax('https://api.github.com/search/users?q='+value).map(payload=>({type:'QUERY_FULFILLED',payload})).takeUntil(action$.ofType('CANCEL_QUERY')).catch(payload=>[{type:'QUERY_REJECTED',error:true,payload}]));

Page 85: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

OK, show me really non-trivial examples

Page 86: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Bidirectional, multiplexed Web Sockets

Page 87: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

classExample{@autobindcheckChange(e){const{value:key,checked}=e.target;this.subs=this.subs||[];

if(checked){consthandler=e=>{constdata=JSON.parse(e.data);if(data.key===key){this.updateValue(key,data.value);}};

this.subs.push({key,handler})

constsocket=this.getSocket(()=>{this.setState({socketOpen:true});this.subs.forEach(({key})=>socket.send(JSON.stringify({type:'sub',key})));});

socket.addEventListener('message',handler);}else{constindex=this.subs.findIndex(x=>x.key===key);if(index!==-1){this.subs.splice(index,1);}

const{socket}=this;if(socket&&socket.readyState===1){socket.send(JSON.stringify({type:'unsub',key}));this.setInactive(key)lif(this.subs.length===0){socket.close();}}}}

componentWillUnMount(){if(this.socket&&this.socket.readyState===1){this.socket.close();}}

Plain JS

getSocket(callback){const{socket}=this;if(socket&&socket.readyState===1){setTimeot(callback);}else{if(this.reconnectId){clearTimeout(this.reconnectId);}

socket=this.socket=newWebSocket(‘ws://localhost:3000');socket.onopen=()=>{callback();};socket.onerror=()=>{this.reconnectId=setTimeout(()=>this.getSocket(callback),1000);this.setState({socketOpen:false});};socket.onclose=(e)=>{if(!e.wasClean){this.reconnectId=setTimeout(()=>this.getSocket(callback),1000);}this.setState({socketOpen:false});};}returnsocket;}}

Page 88: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

Too much code

Page 89: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

As an Epicconstsocket=WebSocketSubject.create('ws://stock/endpoint');

conststockTickerEpic=(action$,store)=>action$.ofType('START_TICKER_STREAM').mergeMap(action=>socket.multiplex(()=>({sub:action.ticker}),()=>({unsub:action.ticker}),msg=>msg.ticker===action.ticker).retryWhen(err=>window.navigator.onLine?Observable.timer(1000):Observable.fromEvent(window,'online')).takeUntil(action$.ofType('CLOSE_TICKER_STREAM').filter(closeAction=>closeAction.ticker===action.ticker)).map(tick=>({type:'TICKER_TICK',tick})));

Page 90: RxJS + Redux + React = Amazing

redux-observable

Jay Phelps | @_jayphelps

• Makes it easier to compose and control complex async tasks, over any amount of time

• You don't need to manage your own Rx subscriptions

• You can use redux tooling

Page 91: RxJS + Redux + React = Amazing

But…

Jay Phelps | @_jayphelps

Page 92: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

You should probably know redux and RxJS in advance

Page 93: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

RxJS has a bit of a learning curve

Page 94: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

“Reactive Programming”

Page 95: RxJS + Redux + React = Amazing

Co-author

Jay Phelps | @_jayphelps

Ben LeshSenior UI Engineer |

@benlesh

Page 96: RxJS + Redux + React = Amazing

Jay Phelps | @_jayphelps

https://redux-observable.js.org

Page 97: RxJS + Redux + React = Amazing
Page 98: RxJS + Redux + React = Amazing

Thanks!

@_jayphelps