Una guia per a principiants de RxJS i Redux-Observable

Bloc

Una guia per a principiants de RxJS i Redux-Observable

Una guia per a principiants de RxJS i Redux-Observable

Redux-Observable és un middleware basat en RxJS per a Redux que permet als desenvolupadors treballar amb accions asíncrones. És una alternativa a redux-thunk i redux-saga.

Aquest article tracta els conceptes bàsics de RxJS, com configurar Redux-Observables i alguns dels seus casos d’ús pràctics. Abans, però, hem d’entendre el Patró d'observador .

Patró d'observador

En el patró Observer, un objecte anomenat Observable o Subjecte, manté una col·lecció de subscriptors anomenada Observers. Quan l’estat dels subjectes canvia, ho notifica a tots els seus observadors.

A JavaScript, l’exemple més senzill seria els emissors d’esdeveniments i els gestors d’esdeveniments.

Quan ho fas .addEventListener, empentes un observador a la col·lecció d’observadors del subjecte. Sempre que succeeix l’esdeveniment, el subjecte notifica a tots els observadors.

RxJS

Segons el lloc web oficial,

RxJS és la implementació de JavaScript ReactiveX , una biblioteca per compondre programes asíncrons i basats en esdeveniments mitjançant l'ús de seqüències observables.

En termes senzills, RxJS és una implementació del patró Observer. També amplia el patró d'observador proporcionant operadors que ens permeten compondre observables i subjectes de manera declarativa.

totes les fruites on comprar

Els observadors, els observables, els operadors i els subjectes són els components bàsics de RxJS. Per tant, vegem cadascuna amb més detall ara.

Observadors

Els observadors són objectes que es poden subscriure a Observables i Subjectes. Després de subscriure’s, poden rebre notificacions de tres tipus: següent, error i complet.

Qualsevol objecte amb la següent estructura es pot utilitzar com a observador.

interface Observer { closed?: boolean; next: (value: T) => void; error: (err: any) => void; complete: () => void; }

Quan l'Observable empenta les notificacions següents, d'error i completes, les .next, .error i .complete de l'Observador s’invoquen mètodes.

observables

Els observables són objectes que poden emetre dades durant un període de temps. Es pot representar mitjançant el diagrama de marbre.

Quan la línia horitzontal representa el temps, els nodes circulars representen les dades emeses per l’observable i la línia vertical indica que l’observable s’ha completat amb èxit.

Els observables poden trobar-se amb un error. La creu representa l'error emès per l'Observable.

Els estats completats i d'error són definitius. Això vol dir que els Observables no poden emetre cap dada després d’haver completat amb èxit o haver trobat un error.

Creació d’un Observable

Els observables es creen mitjançant new Observable constructor que adopta un argument: la funció de subscripció. Els observables també es poden crear mitjançant alguns operadors, però en parlarem més endavant quan parlem d’operadors.

import { Observable } from 'rxjs'; const observable = new Observable(subscriber => { // Subscribe function });

Subscripció a un Observable

Els observables es poden subscriure mitjançant el seu .subscribe mètode i passar un observador.

observable.subscribe({ next: (x) => console.log(x), error: (x) => console.log(x), complete: () => console.log('completed'); });

Execució d’un Observable

La funció de subscripció que hem passat a new Observable El constructor s'executa cada vegada que es subscriu l'Observable.

La funció de subscripció té un argument: el subscriptor. El subscriptor s’assembla a l’estructura d’un observador i té els mateixos 3 mètodes: .next, .error i .complete.

Els observables poden enviar dades a l'observador mitjançant .next mètode. Si l'Observable s'ha completat correctament, pot notificar-ho a l'Observador mitjançant .complete mètode. Si l'Observable ha trobat un error, pot enviar-lo a l'observador mitjançant .error mètode.

// Create an Observable const observable = new Observable(subscriber => { subscriber.next('first data'); subscriber.next('second data'); setTimeout(() => { subscriber.next('after 1 second - last data'); subscriber.complete(); subscriber.next('data after completion'); // console.log(x), error: (x) => console.log(x), complete: () => console.log('completed') }); // Outputs: // // first data // second data // third data // after 1 second - last data // completed

Els observables són Unicast

Els observables són unicast , cosa que significa que Observables pot tenir com a màxim un subscriptor. Quan un observador es subscriu a un Observable, obté una còpia de l’Observable que té el seu propi camí d’execució, cosa que fa que els Observables siguin unicast.

És com veure un vídeo de YouTube. Tots els espectadors miren el mateix contingut de vídeo, però poden veure diferents segments del vídeo.

Exemple : creem un Observable que emet 1 a 10 durant 10 segons. A continuació, subscriviu-vos a l’Observable una vegada immediatament i de nou al cap de 5 segons.

// Create an Observable that emits data every second for 10 seconds const observable = new Observable(subscriber => { let count = 1; const interval = setInterval(() => { subscriber.next(count++); if (count > 10) { clearInterval(interval); } }, 1000); }); // Subscribe to the Observable observable.subscribe({ next: value => { console.log(`Observer 1: ${value}`); } }); // After 5 seconds subscribe again setTimeout(() => { observable.subscribe({ next: value => { console.log(`Observer 2: ${value}`); } }); }, 5000); /* Output Observer 1: 1 Observer 1: 2 Observer 1: 3 Observer 1: 4 Observer 1: 5 Observer 2: 1 Observer 1: 6 Observer 2: 2 Observer 1: 7 Observer 2: 3 Observer 1: 8 Observer 2: 4 Observer 1: 9 Observer 2: 5 Observer 1: 10 Observer 2: 6 Observer 2: 7 Observer 2: 8 Observer 2: 9 Observer 2: 10 */

A la sortida, podeu notar que el segon observador va començar a imprimir des de l’1 tot i que es va subscriure al cap de 5 segons. Això passa perquè el segon observador va rebre una còpia de l'Observable la funció de subscripció es va tornar a invocar. Això il·lustra el comportament unicast d'Observables.

Temes

Un tema és un tipus especial d’observable.

Creació d’un tema

Es crea un assumpte amb el new Subject constructor.

import { Subject } from 'rxjs'; // Create a subject const subject = new Subject();

Subscripció a un assumpte

La subscripció a un assumpte és similar a la subscripció a un Observable: utilitzeu el .subscribe mètode i passar un observador.

subject.subscribe({ next: (x) => console.log(x), error: (x) => console.log(x), complete: () => console.log('done') });

Execució d'un assumpte

A diferència d'Observables, un subjecte crida el seu propi .next, .error i .complete mètodes per enviar dades als observadors.

// Push data to all observers subject.next('first data'); // Push error to all observers subject.error('oops something went wrong'); // Complete subject.complete('done');

Les assignatures són Multidifusió

Els temes ho són multidifusió: diversos observadors comparteixen el mateix tema i el seu camí d’execució. Significa que totes les notificacions s’emeten a tots els observadors. És com veure un programa en directe. Tots els espectadors miren el mateix segment del mateix contingut alhora.

Exemple: creem un assumpte que emet d'1 a 10 durant 10 segons. A continuació, subscriviu-vos a l’Observable una vegada immediatament i de nou al cap de 5 segons.

// Create a subject const subject = new Subject(); let count = 1; const interval = setInterval(() => { subscriber.next(count++); if (count > 10) { clearInterval(interval); } }, 1000); // Subscribe to the subjects subject.subscribe(data => { console.log(`Observer 1: ${data}`); }); // After 5 seconds subscribe again setTimeout(() => { subject.subscribe(data => { console.log(`Observer 2: ${data}`); }); }, 5000); /* OUTPUT Observer 1: 1 Observer 1: 2 Observer 1: 3 Observer 1: 4 Observer 1: 5 Observer 2: 5 Observer 1: 6 Observer 2: 6 Observer 1: 7 Observer 2: 7 Observer 1: 8 Observer 2: 8 Observer 1: 9 Observer 2: 9 Observer 1: 10 Observer 2: 10 */

A la sortida, podeu notar que el segon observador va començar a imprimir des de 5 en lloc de començar per 1. Això passa perquè el segon observador comparteix el mateix tema. Com que es va subscriure al cap de 5 segons, l'assumpte ja ha acabat d'emetre d'1 a 4. Això il·lustra el comportament multidifusió d'un assumpte.

Els subjectes són observables i observadors

Els subjectes tenen el .next, .error i .complete mètodes. Això vol dir que segueixen l'estructura dels observadors. Per tant, un tema també es pot utilitzar com a observador i passar al .subscribe funció dels Observables o altres Temes.

Exemple: creem un Observable i un Subjecte. A continuació, subscriviu-vos a l’observable mitjançant l’assumpte com a observador. Finalment, subscriviu-vos a l’assumpte. Tots els valors emesos per l’observable s’enviaran al subjecte i el subjecte transmetrà els valors rebuts a tots els seus observadors.

// Create an Observable that emits data every second const observable = new Observable(subscriber => { let count = 1; const interval = setInterval(() => { subscriber.next(count++); if (count > 5) { clearInterval(interval); } }, 1000); }); // Create a subject const subject = new Subject(); // Use the Subject as Observer and subscribe to the Observable observable.subscribe(subject); // Subscribe to the subject subject.subscribe({ next: value => console.log(value) }); /* Output 1 2 3 4 5 */

Operadors

Els operadors són els que fan que RxJS sigui útil. Els operadors són funcions pures que retornen un nou Observable. Es poden classificar en dues categories principals:

  1. Operadors de creació
  2. Operadors de canonades

Operadors de creació

Els operadors de creació són funcions que poden crear un nou Observable.

Exemple: podem crear un Observable que emeti cada element d’una matriu mitjançant | | + _ | operador.

from

El mateix pot ser observable mitjançant el diagrama de marbre.

Operadors de canonades

Els operadors canalitzables són funcions que prenen un Observable com a entrada i retornen un Observable nou amb un comportament modificat.

Exemple: prenem l’observable que hem creat amb el const observable = from([2, 30, 5, 22, 60, 1]); observable.subscribe({ next: (value) => console.log('Received', value), error: (err) => console.log(err), complete: () => console.log('done') }); /* OUTPUTS Received 2 Received 30 Received 5 Received 22 Received 60 Received 1 done */ operador. Ara, utilitzant aquest Observable, podem crear un nou Observable que només emeti nombres superiors a 10 mitjançant | | + _ | operador.

from

El mateix es pot representar mitjançant el diagrama de marbre.

Hi ha molts operadors més útils. Podeu veure la llista completa d’operadors juntament amb exemples a la documentació oficial de RxJS aquí .

És fonamental entendre tots els operadors més utilitzats. Aquests són alguns operadors que faig servir sovint:

  1. filter
  2. const greaterThanTen = observable.pipe(filter(x => x > 10)); greaterThanTen.subscribe(console.log, console.log, () => console.log('completed')); // OUTPUT // 11 // 12 // 13 // 14 // 15
  3. mergeMap
  4. switchMap
  5. exhaustMap
  6. map
  7. catchError
  8. startWith
  9. delay
  10. debounce
  11. throttle
  12. interval

Redux Observables

Segons el lloc web oficial,

RxJS middleware basat en Redux . Redacteu i cancel·leu accions asíncrones per crear efectes secundaris i molt més.

A Redux, sempre que s’envia una acció, passa per totes les funcions reductores i es torna un estat nou.

Redux-observable pren totes aquestes accions enviades i nous estats i en crea dos observables: Accions observables from i Estats observables of.

Les accions observables emetran totes les accions que s’envien amb el action$. Els estats observables emetran tots els objectes d'estat nous retornats pel reductor d'arrel.

Epopeies

Segons el lloc web oficial,

És una funció que pren un flux d'accions i retorna un flux d'accions. Accions dins, accions fora.

Les epopeies són funcions que es poden utilitzar per subscriure’s a Actions i States Observables. Un cop subscrites, les epopeies rebran el flux d’accions i estats com a entrada i han de retornar un flux d’accions com a sortida. Accions dins - Accions fora .

state$

És important entendre que totes les accions rebudes a l’Epic ja ho tenen acabat de passar pels reductors .

Dins d’una Epic, podem utilitzar qualsevol patró observable RxJS, i això és el que fa útils els observables redux.

Exemple: podem utilitzar el store.dispatch() per crear un nou intermediari observable. De la mateixa manera, podem crear qualsevol nombre d'observables intermedis, però la sortida final de l'observable final ha de ser una acció, en cas contrari es reduirà una excepció mitjançant redux-observable.

const someEpic = (action$, state$) => { return action$.pipe( // subscribe to actions observable map(action => { // Receive every action, Actions In return someOtherAction(); // return an action, Actions Out }) ) }

Totes les accions emeses per Epics s’envien immediatament mitjançant .filter.

Configuració

En primer lloc, instal·larem les dependències.

const sampleEpic = (action$, state$) => { return action$.pipe( filter(action => action.payload.age >= 18), // can create intermediate observables and streams map(value => above18(value)) // where above18 is an action creator ); }

Creeu una carpeta independent anomenada store.dispatch() per mantenir totes les epopeies. Crea un fitxer nou npm install --save rxjs redux-observable dins del epics i combineu totes les epopeies amb el index.js funció per crear l’èpica arrel. A continuació, exporteu l’èpica arrel.

epics

Creeu un middleware èpic amb el combineEpics funció i passar-la al import { combineEpics } from 'redux-observable'; import { epic1 } from './epic1'; import { epic2 } from './epic2'; const epic1 = (action$, state$) => { ... } const epic2 = (action$, state$) => { ... } export default combineEpics(epic1, epic2); Funció de redux.

createEpicMiddleware

Finalment, passeu l’epopeia de l’arrel a l’epic middleware createStore mètode.

import { createEpicMiddleware } from 'redux-observable'; import { createStore, applyMiddleware } from 'redux'; import rootEpic from './rootEpics'; const epicMiddleware = createEpicMiddlware(); const store = createStore( rootReducer, applyMiddleware(epicMiddlware) );

Alguns casos pràctics d'ús

RxJS té una gran corba d’aprenentatge i la configuració observable per redux empitjora el ja dolorós procés de configuració de Redux. Tot el que fa que Redux sigui observable sembli un excés. Però aquí teniu alguns casos d’ús pràctics que us poden canviar d’opinió.

Al llarg d'aquesta secció, compararé redux-observables amb redux-thunk per mostrar com redux-observables pot ser útil en casos d'ús complexos. No m’agrada redux-thunk, m’encanta i l’utilitzo cada dia.

1. Feu trucades a l'API

Cas d'ús: Feu una trucada a l'API per obtenir comentaris d'una publicació. Mostra els carregadors quan la trucada de l'API està en curs i també gestiona els errors de l'API.

Una implementació redux-thunk tindrà aquest aspecte,

.run

i això és absolutament correcte. Però el creador de l’acció està inflat.

Podem escriure un Epic per implementar el mateix mitjançant redux-observables.

epicMiddleware.run(rootEpic);

Ara ens permet tenir un creador d’acció senzill i net com aquest,

function getComments(postId){ return (dispatch) => { dispatch(getCommentsInProgress()); axios.get(`/v1/api/posts/${postId}/comments`).then(response => { dispatch(getCommentsSuccess(response.data.comments)); }).catch(() => { dispatch(getCommentsFailed()); }); } }

2. Sol·licitud de denúncia

Cas d'ús: Proporcioneu la completació automàtica d'un camp de text trucant a una API sempre que canviï el valor del camp de text. La trucada a l'API s'hauria de fer un segon després que l'usuari hagi deixat d'escriure.

Una implementació redux-thunk tindrà aquest aspecte,

const getCommentsEpic = (action$, state$) => action$.pipe( ofType('GET_COMMENTS'), mergeMap((action) => from(axios.get(`/v1/api/posts/${action.payload.postId}/comments`).pipe( map(response => getCommentsSuccess(response.data.comments)), catchError(() => getCommentsFailed()), startWith(getCommentsInProgress()) ) );

Requereix una variable global function getComments(postId) { return { type: 'GET_COMMENTS', payload: { postId } } } . Quan comencem a utilitzar variables globals, els nostres creadors d’acció ja no són funcions pures. També es fa difícil provar la unitat dels creadors d'accions que utilitzen una variable global.

Podem implementar el mateix amb redux-observable mitjançant let timeout; function valueChanged(value) { return dispatch => { dispatch(loadSuggestionsInProgress()); dispatch({ type: 'VALUE_CHANGED', payload: { value } }); // If changed again within 1 second, cancel the timeout timeout && clearTimeout(timeout); // Make API Call after 1 second timeout = setTimeout(() => { axios.get(`/suggestions?q=${value}`) .then(response => dispatch(loadSuggestionsSuccess(response.data.suggestions))) .catch(() => dispatch(loadSuggestionsFailed())) }, 1000, value); } } operador.

timeout

Ara, els nostres creadors d'acció es poden netejar i, el que és més important, poden tornar a ser funcions pures.

.debounce

3. Sol·liciteu la cancel·lació

Cas d'ús: Continuant amb el cas d'ús anterior, suposem que l'usuari no va escriure res durant 1 segon i vam fer la nostra primera trucada a l'API per obtenir els suggeriments.

Suposem que la pròpia API triga una mitjana de 2-3 segons a retornar el resultat. Ara, si l'usuari escriu alguna cosa mentre la primera trucada a l'API està en curs, al cap d'1 segon, farem la nostra segona API. Podem acabar tenint dues trucades API al mateix temps i es pot crear una condició de carrera.

Per evitar-ho, hem de cancel·lar la primera trucada API abans de fer la segona trucada API.

Una implementació redux-thunk tindrà aquest aspecte,

const loadSuggestionsEpic = (action$, state$) => action$.pipe( ofType('VALUE_CHANGED'), debounce(1000), mergeMap(action => from(axios.get(`/suggestions?q=${action.payload.value}`)).pipe( map(response => loadSuggestionsSuccess(response.data.suggestions)), catchError(() => loadSuggestionsFailed()) )), startWith(loadSuggestionsInProgress()) );

Ara requereix una altra variable global per emmagatzemar el testimoni de cancel·lació d’Axios. Més variables globals = funcions més impures!

Per implementar el mateix mitjançant redux-observable, tot el que hem de fer és substituir function valueChanged(value) { return { type: 'VALUE_CHANGED', payload: { value } } } amb let timeout; var cancelToken = axios.cancelToken; let apiCall; function valueChanged(value) { return dispatch => { dispatch(loadSuggestionsInProgress()); dispatch({ type: 'VALUE_CHANGED', payload: { value } }); // If changed again within 1 second, cancel the timeout timeout && clearTimeout(timeout); // Make API Call after 1 second timeout = setTimeout(() => { // Cancel the existing API apiCall && apiCall.cancel('Operation cancelled'); // Generate a new token apiCall = cancelToken.source(); axios.get(`/suggestions?q=${value}`, { cancelToken: apiCall.token }) .then(response => dispatch(loadSuggestionsSuccess(response.data.suggestions))) .catch(() => dispatch(loadSuggestionsFailed())) }, 1000, value); } } .

.mergeMap

Com que no requereix cap canvi als nostres creadors d'accions, poden continuar sent funcions pures.

Concloure

Si esteu desenvolupant una aplicació Redux que comporta casos d’ús tan complexos, us recomanem que utilitzeu Redux-Observables. Al cap i a la fi, els avantatges d’utilitzar-lo són directament proporcionals a la complexitat de la vostra aplicació, i es desprèn dels casos d’ús pràctics esmentats anteriorment.

Publicat originalment per Praveen a https://www.freecodecamp.org

#redux #rxjs #reactjs #javascript #webdev