Crearea de aplicații reactive cu Redux, RxJS și Redux-Observable în React Native
Publicat: 2022-03-11În ecosistemul în creștere al aplicațiilor web și mobile bogate și puternice, există din ce în ce mai multe stări care trebuie gestionate, cum ar fi utilizatorul actual, lista de articole încărcate, starea de încărcare, erori și multe altele. Redux este o soluție la această problemă prin păstrarea stării într-un obiect global.
Una dintre limitările Redux este că nu acceptă comportamentul asincron din cutie. O soluție pentru aceasta este redux-observable
, care se bazează pe RxJS, o bibliotecă puternică pentru programare reactivă în JavaScript. RxJS este o implementare a ReactiveX, un API pentru programare reactivă care a apărut la Microsoft. ReactiveX combină unele dintre cele mai puternice caracteristici ale paradigmei reactive, programarea funcțională, modelul observator și modelul iterator.
În acest tutorial, vom afla despre Redux și despre utilizarea sa cu React. Vom explora, de asemenea, programarea reactivă folosind RxJS și modul în care poate face munca asincronă obositoare și complexă foarte simplă.
În cele din urmă, vom învăța redux-observable, o bibliotecă care folosește RxJS pentru a face lucru asincron și apoi vom construi o aplicație în React Native folosind Redux și redux-observable.
Redux
După cum se descrie pe GitHub, Redux este „un container de stare previzibil pentru aplicațiile JavaScript”. Oferă aplicațiilor JavaScript o stare globală, păstrând starea și acțiunile departe de componentele React.
Într-o aplicație tipică React fără Redux, trebuie să transmitem date de la nodul rădăcină către copii prin proprietăți sau elemente de props
. Acest flux de date este gestionabil pentru aplicațiile mici, dar poate deveni foarte complex pe măsură ce aplicația dvs. crește. Redux ne permite să avem componente independente unele de altele, astfel că o putem folosi ca o singură sursă de adevăr.
Redux poate fi folosit în React folosind react-redux
, care oferă legături pentru componentele React pentru a citi date din Redux și a trimite acțiuni pentru a actualiza starea Redux.
Redux poate fi descris ca trei principii simple:
1. Sursa unică a adevărului
Starea întregii aplicații este stocată într-un singur obiect. Acest obiect din Redux este deținut de un magazin. Ar trebui să existe un singur magazin în orice aplicație Redux.
» console.log(store.getState()) « { user: {...}, todos: {...} }
Pentru a citi date de la Redux în componenta dvs. React, folosim funcția de connect
din react-redux
. connect
are patru argumente, toate fiind opționale. Deocamdată, ne vom concentra pe primul, numit mapStateToProps
.
/* UserTile.js */ import { connect } from 'react-redux'; class UserTile extends React.Component { render() { return <p>{ this.props.user.name }</p> } } function mapStateToProps(state) { return { user: state.user } } export default connect(mapStateToProps)(UserTile)
În exemplul de mai sus, mapStateToProps
primește starea globală Redux ca prim argument și returnează un obiect care va fi îmbinat cu elementele de recuzită transmise <UserTile />
de componenta sa părinte.
2. Starea este numai pentru citire
Starea Redux este doar în citire pentru componentele React și singura modalitate de a schimba starea este emiterea unei acțiuni . O acțiune este un obiect simplu care reprezintă o intenție de a schimba starea. Fiecare obiect de acțiune trebuie să aibă un câmp de type
, iar valoarea trebuie să fie un șir. În afară de asta, conținutul acțiunii depinde în totalitate de dvs., dar majoritatea aplicațiilor urmează un format flux-standard-acțiune, care limitează structura unei acțiuni la doar patru taste:
-
type
Orice identificator de șir pentru o acțiune. Fiecare acțiune trebuie să aibă o acțiune unică. -
payload
Date opționale pentru orice acțiune. Poate fi de orice moment și conține informații despre acțiune. -
error
Orice proprietate booleană opțională setată la true dacă acțiunea reprezintă o eroare. Aceasta este analogă cu oPromise. string
identificator dePromise. string
pentru o acțiune. Fiecare acțiune trebuie să aibă o acțiune unică. Prin convenție, cânderror
estetrue
,payload
ar trebui să fie un obiect de eroare. -
meta
Meta poate fi orice tip de valoare. Este destinat oricărei informații suplimentare care nu face parte din sarcina utilă.
Iată două exemple de acțiuni:
store.dispatch({ type: 'GET_USER', payload: '21', }); store.dispatch({ type: 'GET_USER_SUCCESS', payload: { user: { id: '21', name: 'Foo' } } });
3. Starea este schimbată cu funcții pure
Starea globală Redux este modificată folosind funcții pure numite reductoare. Un reductor preia starea și acțiunea anterioară și returnează starea următoare. Reductorul creează un nou obiect de stare în loc să îl modifice pe cel existent. În funcție de dimensiunea aplicației, un magazin Redux poate avea un singur reductor sau mai multe reductoare.
/* store.js */ import { combineReducers, createStore } from 'redux' function user(state = {}, action) { switch (action.type) { case 'GET_USER_SUCCESS': return action.payload.user default: return state } } function todos(state = [], action) { switch (action.type) { case 'ADD_TODO_SUCCESS': return [ ...state, { id: uuid(), // a random uuid generator function text: action.text, completed: false } ] case 'COMPLETE_TODO_SUCCESS': return state.map(todo => { if (todo.id === action.id) { return { ...todo, completed: true } } return todo }) default: return state } } const rootReducer = combineReducers({ user, todos }) const store = createStore(rootReducer)
Similar cu citirea din stat, putem folosi o funcție de connect
pentru a trimite acțiuni.
/* UserProfile.js */ class Profile extends React.Component { handleSave(user) { this.props.updateUser(user); } } function mapDispatchToProps(dispatch) { return ({ updateUser: (user) => dispatch({ type: 'GET_USER_SUCCESS', user, }), }) } export default connect(mapStateToProps, mapDispatchToProps)(Profile);
RxJS
Programare reactiva
Programarea reactivă este o paradigmă de programare declarativă care se ocupă de fluxul de date în „ fluxuri ” și de propagarea și modificările acestuia. RxJS, o bibliotecă pentru programarea reactivă în JavaScript, are un concept de observabile , care sunt fluxuri de date la care un observator se poate abona , iar acest observator primește date în timp.
Un observator al unui observabil este un obiect cu trei funcții: next
, error
, și complete
. Toate aceste funcții sunt opționale.
observable.subscribe({ next: value => console.log(`Value is ${value}`), error: err => console.log(err), complete: () => console.log(`Completed`), })
Funcția .subscribe
poate avea și trei funcții în loc de un obiect.
observable.subscribe( value => console.log(`Value is ${value}`), err => console.log(err), () => console.log(`Completed`) )
Putem crea un nou observabil prin crearea unui obiect al unui observable
, trecând o funcție care primește un abonat, alias observator. Abonatul are trei metode: next
, error
, and complete
. Abonatul poate suna în continuare cu o valoare de câte ori este necesar și, în final, complete
sau error
. După apelarea complete
sau error
, observabilul nu va împinge nicio valoare în flux.
import { Observable } from 'rxjs' const observable$ = new Observable(function subscribe(subscriber) { const intervalId = setInterval(() => { subscriber.next('hi'); subscriber.complete() clearInterval(intervalId); }, 1000); }); observable$.subscribe( value => console.log(`Value is ${value}`), err => console.log(err) )
Exemplul de mai sus va afișa Value is hi
după 1000 de milisecunde.
Crearea manuală a unui element observabil de fiecare dată poate deveni pronunțată și plictisitoare. Prin urmare, RxJS are multe funcții pentru a crea un observabil. Unele dintre cele mai frecvent utilizate sunt of
, from
, și ajax
.
de
of
ia o secvență de valori și o convertește într-un flux:

import { of } from 'rxjs' of(1, 2, 3, 'Hello', 'World').subscribe(value => console.log(value)) // 1 2 3 Hello World
din
from
convertește aproape orice într-un flux de valori:
import { from } from 'rxjs' from([1, 2, 3]).subscribe(console.log) // 1 2 3 from(new Promise.resolve('Hello World')).subscribe(console.log) // 'Hello World' from(fibonacciGenerator).subscribe(console.log) // 1 1 2 3 5 8 13 21 ...
ajax
ajax
preia un șir URL sau creează un observabil care face o solicitare HTTP. ajax
are o funcție ajax.getJSON
, care returnează doar obiectul de răspuns imbricat din apelul AJAX fără alte proprietăți returnate de ajax()
:
import { ajax } from 'rxjs/ajax' ajax('https://jsonplaceholder.typicode.com/todos/1').subscribe(console.log) // {request, response: {userId, id, title, completed}, responseType, status} ajax.getJSON('https://jsonplaceholder.typicode.com/todos/1').subscribe(console.log) // {userId, id, title, completed} ajax({ url, method, headers, body }).subscribe(console.log) // {...}
Există mai multe moduri de a face un observabil (puteți vedea lista completă aici).
Operatori
Operatorii sunt o adevărată putere a RxJS, care are un operator pentru aproape orice veți avea nevoie. Deoarece RxJS 6, operatorii nu sunt metode pe obiectul observabil, ci funcții pure aplicate pe observabil folosind o metodă .pipe
.
Hartă
map
preia o funcție cu un singur argument și aplică o proiecție pe fiecare element din flux:
import { of } from 'rxjs' import { map } from 'rxjs/operators' of(1, 2, 3, 4, 5).pipe( map(i=> i * 2) ).subscribe(console.log) // 2, 4, 6, 8, 10
filtru
filter
preia un singur argument și elimină valorile din flux care returnează false pentru funcția dată:
import { of } from 'rxjs' import { map, filter } from 'rxjs/operators' of(1, 2, 3, 4, 5).pipe( map(i => i * i), filter(i => i % 2 === 0) ).subscribe(console.log) // 4, 16
flatMap
Operatorul flatMap
preia o funcție care mapează fiecare articol din steam într-un alt flux și aplatizează toate valorile acestor fluxuri:
import { of } from 'rxjs' import { ajax } from 'rxjs/ajax' import { flatMap } from 'rxjs/operators' of(1, 2, 3).pipe( flatMap(page => ajax.toJSON(`https://example.com/blog?size=2&page=${page}`)), ).subscribe(console.log) // [ { blog 1 }, { blog 2 }, { blog 3 }, { blog 4 }, { blog 5 }, { blog 6 } ]
combina
merge
elementele din două fluxuri în ordinea în care ajung:
import { interval, merge } from 'rxjs' import { pipe, take, mapTo } from 'rxjs/operators' merge( interval(150).pipe(take(5), mapTo('A')), interval(250).pipe(take(5), mapTo('B')) ).subscribe(console.log) // ABAABAABBB
O listă completă a operatorilor este disponibilă aici.
Redux-Observabil
Prin proiectare, toate acțiunile din Redux sunt sincrone. Redux-observable este un middleware pentru Redux care utilizează fluxuri observabile pentru a efectua lucrări asincrone și apoi trimite o altă acțiune în Redux cu rezultatul acelei lucrări asincrone.
Redux-observable se bazează pe ideea de Epics . O epopee este o funcție care preia un flux de acțiuni și, opțional, un flux de stare și returnează un flux de acțiuni.
funcție (acțiune$: observabil
Prin convenție, fiecare variabilă care este un flux (_aka _observable
) se termină cu $
. Înainte de a putea folosi redux-observable, trebuie să-l adăugăm ca middleware în magazinul nostru. Deoarece epopeele sunt fluxuri de observabile și fiecare acțiune care iese din acest abur este transmisă înapoi în flux, întoarcerea aceleiași acțiuni va avea ca rezultat o buclă infinită.
const epic = action$ => action$.pipe( filter(action => action.type === 'FOO'), mapTo({ type: 'BAR' }) // not changing the type of action returned // will also result in an infinite loop ) // or import { ofType } from 'redux-observable' const epic = action$ => action$.pipe( ofType('FOO'), mapTo({ type: BAZ' }) )
Gândiți-vă la această arhitectură reactivă ca la un sistem de țevi în care ieșirea fiecărei țevi este alimentată înapoi în fiecare țeavă, inclusiv pe ea însăși, și, de asemenea, în reductoarele Redux. Filtrele de deasupra acestor țevi sunt cele care decid ce intră și ce este blocat.
Să vedem cum ar funcționa o epopee de ping-pong. Este nevoie de un ping, îl trimite la server și, după ce solicitarea este finalizată, trimite un pong înapoi la aplicație.
const pingEpic = action$ => action$.pipe( ofType('PING'), flatMap(action => ajax('https://example.com/pinger')), mapTo({ type: 'PONG' }) ) Now, we are going to update our original todo store by adding epics and retrieving users. import { combineReducers, createStore } from 'redux' import { ofType, combineEpics, createEpicMiddleware } from 'redux-observable'; import { map, flatMap } from 'rxjs/operators' import { ajax } from 'rxjs/ajax' // ... /* user and todos reducers defined as above */ const rootReducer = combineReducers({ user, todos }) const epicMiddleware = createEpicMiddleware(); const userEpic = action$ => action$.pipe( ofType('GET_USER'), flatMap(() => ajax.getJSON('https://foo.bar.com/get-user')), map(user => ({ type: 'GET_USER_SUCCESS', payload: user })) ) const addTodoEpic = action$ => action$.pipe( ofType('ADD_TODO'), flatMap(action => ajax({ url: 'https://foo.bar.com/add-todo', method: 'POST', body: { text: action.payload } })), map(data => data.response), map(todo => ({ type: 'ADD_TODO_SUCCESS', payload: todo })) ) const completeTodoEpic = action$ => action$.pipe( ofType('COMPLETE_TODO'), flatMap(action => ajax({ url: 'https://foo.bar.com/complete-todo', method: 'POST', body: { id: action.payload } })), map(data => data.response), map(todo => ({ type: 'COMPLEE_TODO_SUCCESS', payload: todo })) ) const rootEpic = combineEpics(userEpic, addTodoEpic, completeTodoEpic) const store = createStore(rootReducer, applyMiddleware(epicMiddleware)) epicMiddleware.run(rootEpic);
_Important: Epopeele sunt la fel ca orice alte fluxuri observabile în RxJS. Ele pot ajunge într-o stare completă sau de eroare. După această stare, epicul și aplicația dvs. nu vor mai funcționa. Deci, trebuie să prindeți fiecare eroare potențială din abur. Puteți utiliza operatorul __catchError__
pentru aceasta. Mai multe informații: Gestionarea erorilor în redux-observable .
O aplicație reactivă Todo
Cu o interfață de utilizare adăugată, o aplicație demonstrativă (minimală) arată cam așa:
Un tutorial React, Redux și RxJS rezumat
Am învățat ce sunt aplicațiile reactive. De asemenea, am aflat despre Redux, RxJS și redux-observable și chiar am creat o aplicație reactivă Todo în Expo cu React Native. Pentru dezvoltatorii React și React Native, tendințele actuale oferă câteva opțiuni foarte puternice de gestionare a stării.
Încă o dată, codul sursă pentru această aplicație este pe GitHub. Simțiți-vă liber să vă împărtășiți părerile despre managementul de stat pentru aplicațiile reactive în comentariile de mai jos.