RxJS est une bibliothèque JavaScript réactive qui permet aux développeurs de créer des applications robustes avec une logique et un code simples. Il offre un ensemble de fonctionnalités puissantes pour simplifier la programmation asynchrone, telles que les observables, les opérateurs et la programmation réactive.
Les observables sont un concept du framework Angular. Par conséquent, Observables peut être utilisé pour créer des services réactifs afin de faciliter la propagation des mises à jour de données entre les composants qui consomment ces données.
Afin de réagir à des événements ou des données de manière asynchrone (c’est-à-dire sans attendre la fin d’une tâche, comme un appel HTTP, avant de continuer à exécuter la ligne de code suivante), plusieurs méthodes existent depuis plusieurs années. Par exemple, il existe des systèmes de rappel ou des Promesses. Utilisez l’API RxJS, fournie en Angular et très intégrée, la méthode recommandée est la méthode Observables.
L’utilisation d’observables pour modéliser votre état permet un système de propagation d’état hautes performances basé sur des abonnements de référencement avancés.
React-RxJS vous permet d’exprimer pleinement le comportement dynamique de l’état de l’application défini. React-RxJS fournit une API basée sur des hooks, offrant une prise en charge de première classe pour React.Suspense et Error Boundaries. De plus, tous les hooks créés avec React-RxJS peuvent être utilisés pour l’état partagé.
Qu’est-ce que RxJS ?
Aujourd’hui, quand on parle d’asynchronisme, on pense vite aux appels HTTP vers des ressources externes. Mais ce n’est qu’une réaction à un événement qui se produira à un moment donné. De la même manière, nous pouvons réagir à tout autre événement sans savoir quand il se produira (clic, saisie..).
Imaginez maintenant un code qui compte le nombre de clics de l’utilisateur : c’est simple, une variable initialisée à 0 et incrémentée à chaque rappel enregistré dans l’événement onclick.
Imaginez ensuite un code qui calcule le temps (en millisecondes) entre les clics et se met à jour en fonction du dernier clic. Imaginons que la différence d’horodatage des deux variables on va calculer, puis on va s’inverser dès qu’un nouveau clic arrive… Ça marche, mais ce n’est pas du tout optimisé
C’est là que RxJS peu nous aider. Cette bibliothèque nous permettra de traiter tous ces événements comme un flux, qui ressemble plus ou moins à un tableau. Cerise sur le gâteau, il va nous donner tout un ensemble de moyens pour traiter ce flux, le filtrer, le transformer, et même le combiner avec d’autres flux !
RxJS est une bibliothèque pour écrire des programmes asynchrones et basés sur des événements en utilisant des séquences observables. Il fournit un type primitif, Observable, des types satellites (Observer, Scheduler, Subjects) et des opérateurs inspirés de Array#extras (map, filter, reduce, every, etc.) pour permettre le traitement asynchrone des événements dans les collections.
ReactiveX combine le modèle Observer avec le modèle Iterator et la programmation fonctionnelle avec des collections pour répondre au besoin d’un moyen idéal de gérer les séquences d’événements.
Les concepts essentiels de RxJS qui résolvent la gestion des événements asynchrones sont les suivants.
- Observable : représente l’idée d’une collection invocable de valeurs ou d’événements futurs.
- Observer : est une collection de callbacks qui sait écouter les valeurs délivrées par l’Observable.
- Abonnement : représente l’exécution d’un Observable ; il est principalement utile pour annuler l’exécution.
- Opérateurs : sont des fonctions pures qui permettent un style de programmation fonctionnel pour traiter les collections avec des opérations telles que map, filter, concat, reduce, etc.
- Objet : est l’équivalent d’un EventEmitter et le seul moyen de multidiffusion d’une valeur ou d’un événement à plusieurs observateurs.
Planificateurs : sont des répartiteurs centralisés pour contrôler la concurrence, nous permettant de coordonner le moment où le calcul se produit, par exemple sur setTimeout ou requestAnimationFrame ou autres.
Le jargon technique
Tout d’abord, analysons le terme « flux de données asynchrone » :
Flux : Une séquence de données disponibles au fil du temps. Le streaming vidéo est un bon exemple : vous pouvez regarder la vidéo sans attendre la fin du téléchargement.
Données : Données brutes au format JavaScript, telles que des nombres, des chaînes, des objets…
Asynchrone : En Javascript, c’est un mécanisme qui permet d’appeler une fonction et d’enregistrer le callback qui sera exécuté en fin de traitement. Cela permet au script de continuer l’exécution sans attendre cette réponse. Les situations les plus courantes sont les appels XHR (Ajax), les événements DOM et les promesses.
Avec RxJS, nous utilisons des séquences observables pour représenter les flux de données asynchrones. Vous pouvez utiliser des observables en mode push-pull :
- Quand on applique le modèle push, on s’abonne au flux source et on y réagit lorsque les données sont disponibles.
- Pour le mode pull, vous synchronisez les abonnements. Par exemple, cela se produit lorsque nous utilisons des fonctions de tableau (map(),…).
La comparaison avec les promesses est essentielle, mais aussi regrettable. Cependant, cela reste un bon point de départ pour comprendre l’observable. La comparaison entre les abonnements promis alors et observables montre notre intérêt à développer l’asynchrone. Cependant, cette comparaison ne va pas plus loin. Les promesses sont toujours en multidiffusion et la résolution ou le rejet des promesses se fait toujours de manière asynchrone. En revanche, les objets observables sont parfois multicast, ils sont généralement asynchrones,…
Tout cela est pour vous faire savoir que lorsque vous utilisez RxJS, vous avez d’autres questions à vous poser lorsque vous utilisez des observables. Mais revenons d’abord à la source des observables.
Pourquoi apprendre Rxjs ?
Apprendre RxJS et la programmation réactive est difficile. Il y a beaucoup de concepts, une énorme surface d’API et un changement fondamental d’un style impératif vers un style déclaratif.
Le site vise à rendre ces concepts faciles à comprendre, les exemples sont clairs et faciles à explorer, et à fournir des références aux meilleurs documents liés à RxJS sur le Web. Le but est de compléter les documents officiels et les supports d’apprentissage existants, tout en offrant une nouvelle perspective pour surmonter les obstacles et résoudre les problèmes.
Apprendre Rx peut être difficile, mais cela en vaut vraiment la peine !
Le premier reflex est bien sûr d’aller sur la page Web principale de RxJS :
- https://rxjs-dev.firebaseapp.com/guide/overview
La documentation est très détaillée et bien présentée, la plupart des concepts sont couverts en profondeur, ce qui est génial mais parfois cela peut rendre plus difficile la compréhension rapide.
Donc, en lisant certains des concepts les plus complexes, j’ai découvert qu’il y avait beaucoup plus de didacticiels conviviaux pour la première fois :
- https://www.learnrxjs.io/ (Il est particulièrement utile pour apprendre ou vérifier rapidement comment travailler avec certains des opérateurs Rx)
Mais qu’est-ce que l’observable ?
Tout simplement, un Observable est un objet qui envoie les informations auxquelles nous voulons répondre. Par exemple, ces informations peuvent provenir d’un champ de texte où l’utilisateur saisit des données, ou de la progression d’un téléchargement de fichier. Ils peuvent aussi provenir de la communication avec le serveur : les clients HTTP (vous le verrez dans les chapitres suivants) utilisent des Observables.
Pour cet Observable, nous associons un Observer – un bloc de code qui est exécuté à chaque fois que l’Observable envoie un message. Observable émet trois types d’informations : données, erreur ou message complet. Du coup, tout observateur peut avoir trois fonctions : l’une est de répondre à chaque type d’information.
Pour créer un cas réel simple, vous allez créer un Observable dans AppComponent, qui enverra un nouveau numéro toutes les secondes. Ensuite, vous observerez cet Observable et l’afficherez dans le DOM : de cette façon, vous pourrez dire à l’utilisateur quand il a vu l’application.
Un Observable est un type spécial d’objet qui émet une séquence d’événements au fil du temps. Il représente le flux de données. Le flux de données se termine par un état complet ou d’erreur lorsqu’il est émis, il met fin à la durée de vie de l’observable et devient inutile.
Pour chaque observable, il doit y avoir un observateur, qui est essentiellement un ou plusieurs rappels qui s’exécutent lorsque l’observable émet une nouvelle valeur et des rappels complets et d’erreur pour réagir à l’état complet ou à l’état d’erreur.
Les observables sont froids par défaut, ce qui signifie qu’ils ne fonctionneront pas tant que vous n’aurez pas souscrit (enregistré) au moins un observateur, afin qu’ils puissent commencer à recevoir des séquences de données de l’Observable.
Lorsque vous utilisez Rxjs, la plupart du temps, vous créez (générez) des observables à l’aide d’opérateurs qui peuvent générer des observables pour des opérations spécifiques, par exemple émettre toutes les valeurs du tableau une par une à l’aide de l’opérateur from.
Pourquoi utiliser React Rx ?
En ce qui concerne la combinaison de React avec Rx .
- RxJS est parfait pour les requêtes asynchrones complexes et les réactions aux événements. Avec RxJS et lodash, il est facile d’écrire du code propre dans ces cas.
- Vous pouvez utiliser RxJS pour traiter et limiter les événements utilisateur et, par conséquent, mettre à jour l’état de l’application.
- Avec React 17, il est idéal pour créer une communication entre les composants.
- Il apporte des méthodes qui permettent à un développeur de créer des flux de données et de manipuler sur ces flux. Et il y a tellement d’options de manipulation ou de création que vous avez un large champ de jeu (par exemple vous pouvez l’utiliser avec des Websockets).
- Pour tout ce qui implique de travailler avec le concept du temps, ou lorsque vous devez raisonner sur les valeurs/événements historiques d’un observable (et pas seulement le dernier), RxJS est recommandé car il fournit plus de primitives de bas niveau.
Quand ne pas utiliser Rx ?
- La maintenance – votre équipe est-elle expérimentée avec Rx ? Sinon, il peut être assez difficile pour les nouveaux arrivants d’apprendre Rx js, car il a un parcours d’apprentissage assez raide.
- C’est assez difficile à déboguer. L’enregistreur ne montre parfois que les fonctions des profondeurs du code source, vous devez donc vous y plonger en profondeur – ou simplement deviner – ce qui n’a pas fonctionné.
- Aussi pour certains projets, ce serait juste exagéré.
- Il a aussi des avantages et des inconvénients, mais il y a tellement d’opérateurs que vous pouvez combiner dans un seul tuyau et vous perdre assez facilement (débogage).
Quels sont les problèmes possibles dans lesquels je peux m’embarquer ?
Le problème avec RxJS est peut-être qu’il ne s’agit que d’un utilitaire, et qu’il ne montre pas aux gens comment concevoir leur application pour qu’elle résolve certains problèmes difficiles courants :
- inspecter l’état actuel de l’application,
- combiner des flux dont les valeurs sont dans le désordre,
- combiner des flux dont les valeurs pourraient être incomplètes,
- gérer les temporisations,
- traiter les données de flux obsolètes,
- l’annulation des opérations de flux en vol,
- comportements conditionnels avec des flux,
- réessayer la mécanique.
Étant donné que RxJS est calqué sur la programmation monadique, tous ces problèmes pourraient être résolus s’ils sont correctement architecturés, mais ils ne sont pas triviaux sans une expérience appropriée.
Qu’en est-il de MobX, n’est-il pas déjà implémenté comme le stockage Rx ?
MobX est un gestionnaire d’état et RxJS est une bibliothèque pour gérer les événements asynchrones.
Quant à MobX, la documentation dit :
MobX peut-il être combiné avec RxJS ?
Oui, vous pouvez utiliser toStream et fromStream de mobx-utils pour utiliser RxJS et d’autres observables compatibles TC 39 avec mobx.
Quand utiliser RxJS au lieu de MobX ?
Pour tout ce qui implique de travailler explicitement avec le concept de temps, ou lorsque vous devez raisonner sur les valeurs / événements historiques d’un observable (et pas seulement le dernier), RxJS est recommandé car il fournit plus de primitives de bas niveau. Chaque fois que vous souhaitez réagir à un état plutôt qu’à des événements, MobX offre une approche plus simple et de plus haut niveau. En pratique, combiner RxJS et MobX peut aboutir à des constructions vraiment puissantes. Utilisez par exemple RxJS pour traiter et limiter les événements utilisateur et, par conséquent, mettre à jour l’état. Si l’état a été rendu observable par MobX, il se chargera alors de mettre à jour l’interface utilisateur et les autres dérivations en conséquence.
Il peut également être judicieux d’utiliser MobX avec RxJS, mais vous devrez installer d’autres dépendances.
React avec RxJS :
Dans le répertoire du projet racine, installez les dépendances nécessaires :
npm installer rxjs
npm installer rxjs-compat
Concentrons-nous sur un exemple concret avec un appel d’extraction qui nécessite qu’un développeur effectue plusieurs étapes de manipulation de données et des appels d’API supplémentaires pour obtenir les données nécessaires au chargement de l’application.
Voici mon composant principal connecté à un état redux ;
class Main extends Component {
subscription;
constructor() {
super();
this.search$ = new Subject();
this.search = this.search$.asObservable().pipe(
debounceTime(500),
);
}
componentDidMount() {
this.subscription = this.search.subscribe((text) => {
this.callApiToGetHeroes(text);
});
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
onSearch = (e) => {
this.search$.next(e.target.value);
}
render() {
const { listOfHeroes, team } = this.props;
return (
<div className="main_view">
<Header />
<SearchBar onSearch={this.onSearch} />
<div className="main_view_list_container">
<HeroList heroesList={listOfHeroes} />
<ChosenList chosenList={team} />
</div>
</div>
);
}
}
Main.propTypes = {
listOfHeroes: PropTypes.arrayOf(PropTypes.object),
team: PropTypes.arrayOf(PropTypes.object),
getHeroes: PropTypes.func,
};
Main.defaultProps = {
listOfHeroes: [],
team: [],
getHeroes: null,
};
const mapStateToProps = (state) => ({ team: state.team, listOfHeroes: state.list });
const mapDispatchToProps = (dispatch) => ({
getHeroes: () => dispatch(getHeroesAction()),
});
export default connect(mapStateToProps, mapDispatchToProps)(Main);
Pour créer une entrée réactive dans le composant principal, il doit avoir un créateur observable – dans ce cas, un sujet.
Pour limiter les demandes d’API, j’ai ajouté debounceTime (500) à l’observable afin qu’il attende 500 ms avant un autre appel.
Le Sujet n’est qu’une façon de créer un observable.
Remarque importante : unsubscribe() d’un observable sur componentWillUnmount est utilisé pour empêcher les observables d’exister en dehors du composant après sa destruction afin d’éviter les fuites de mémoire.
Appel API :
import 'babel-polyfill';
import { fromFetch } from 'rxjs/fetch';
import {
switchMap,
map,
tap,
flatMap,
} from 'rxjs/operators';
import { forkJoin } from 'rxjs';
import 'rxjs/add/observable/of';
import { ADD_TO_CHOSEN, REMOVE_FROM_CHOSEN, FETCH_HEROES_SUCCESS } from './types';
const fetchUrl = 'https://swapi.co/api/people/';
const imgUrl = 'https://i.pravatar.cc/200?img=';
export const addHero = (hero) => ({ type: ADD_TO_CHOSEN, hero });
export const removeHero = (hero) => ({ type: REMOVE_FROM_CHOSEN, hero });
export const getHeros = (heros) => ({ type: FETCH_HEROES_SUCCESS, heros });
const getWorlds = (list) =>
forkJoin(list.map((hero) =>
fromFetch(hero.homeworld)
.pipe(
switchMap((resp) => resp.json()),
map((resp) => ({ ...hero, homeworld: resp.name })),
)));
const getSpecies = (list) =>
forkJoin(list.map((hero) =>
fromFetch(hero.species[0])
.pipe(
switchMap((resp) => resp.json()),
map((resp) => ({ ...hero, species: [resp.name] })),
)));
const handleResponse = (resp) =>{
if (resp.ok) {
return resp.json();
} else {
return of({ error: true, message: `Error ${resp.status}` });
}
}
export const getHeroesAction = () =>
(dispatch) =>
fromFetch(fetchUrl)
.pipe(
switchMap((response) => handleResponse(response)),
map((response) => response.results),
flatMap((response) => getWorlds(response)),
flatMap((response) => getSpecies(response)),
tap((completeHeroes) => dispatch(getHeros(completeHeroes))),
catchError((err) => {
console.error(err);
return of({ error: true, message: err.message });
}),
);
Je vais donc essayer d’expliquer ce qui se passe là-bas
Nous commençons par fromFetch qui convertit l’appel fetch de base en Observable<Response> (<type d’observable>) cela peut également être fait avec fromPromise (fonctionne déprécié pour la version < 6.) ou à partir de .
L’ opérateur pipe permet de manipuler le flux de données avant l’émission observable avec certains des opérateurs Rx. Dans ce cas, switchMap gère la réponse du serveur (il annule également les observables précédents). Et l’ opérateur map permet de transformer l’observable.
Ensuite, la magie Rx commence – le problème est la structure des données de réponse – certaines des valeurs de l’objet héros ne sont que des URL pour obtenir les valeurs d’une autre source – avec Rx, nous pouvons tout faire en un seul flux ! <B
forkJoin fonctionne un peu comme Promise.all – il se joint et attend que toutes les observables internes soient émises, puis renvoie une nouvelle observable avec des données provenant d’observables internes. C’est parfait pour ce cas avec plusieurs requêtes http.
flatMap est utilisé ici pour aplatir les observables créés avec forkJoin et continuer sur le flux ;
Et les deux opérateurs suivants sont assez simples – tap
- permet de créer des effets secondaires – mais cela ne change en rien l’observable et
catchError permet d’attraper une erreur et de la transformer comme nous le souhaiterions.
Et pour les tests ?
Eh bien, c’est facile – juste pour tester un flux dans une application react, le meilleur moyen c’est de supprimer le pipeline du composant et de le conserver dans un fichier séparé avec les autres opérateurs – puis de tester L’effet de chaque flux devient assez simple. par example:
export const simpleMapTest = (observable$) => {
return observable$.pipe(
map(data => data + ' search'),
)
};
Et l’épreuve :
it('should change value of the observable', (done) => {
const observable$ = of('text');
simpleMapTest(observable$).subscribe((value) => {
expect(value).toBe('text search');
done();
});
});
Quant au test des composants, nous ne pouvons pas vraiment souscrire aux observables des composants mais nous pouvons tester les effets de nos flux sur ce composant. Prenons l’opérateur anti-rebond de l’entrée que nous avons créée plus tôt. Le test de cette action ressemblerait à ceci :
it('should execute call API to get Heroes only once', () => {
const wrapper = shallow(<MainComponent />);
const shallowComponent = wrapper.instance();
shallowComponent.callApiToGetHeroes = jest.fn();
wrapper.update();
shallowComponent.onSearch({ target: { value: 'text' } });
shallowComponent.onSearch({ target: { value: 'text1' } });
shallowComponent.onSearch({ target: { value: 'text3' } });
setTimeout(() => {
expect(shallowComponent.callApiToGetHeroes).toHaveBeenCalledTimes(1);
expect(shallowComponent.callApiToGetHeroes).toHaveBeenCalledWidth('text3');
}, 0);
});
A quoi sert RxJS ?
ReactiveX combine le modèle d’observateur avec le modèle d’itérateur et la programmation et les collections fonctionnelles pour répondre aux besoins de gestion de la séquence d’événements.
Par exemple, un événement se produit lorsque l’utilisateur ou le navigateur modifie la page de quelque manière que ce soit. Lorsque la page se charge, cela s’appelle un événement. Lorsque l’utilisateur clique sur un bouton, le clic est également un événement.
Vous avez dit React observable ?
Comprendre React observable : RxJS, une bibliothèque pour la programmation réactive en JavaScript, a un concept d’observables, qui sont des flux de données sur lesquels un observateur peut s’abonner et à qui cet observateur est livré des données au fil du temps. Un observateur d’un observable est un objet avec trois fonctions : next , error et complete .
const observer = {
next: (data) => {
/* faire quelque chose avec les données */ },
error: (err) => {
/* gérer l'erreur */ },
complete: () => {
/* effectuer une action lorsque c'est terminé */ }
};
Résumé:
Rx.js est un excellent outil entre les mains de développeurs expérimentés, il peut être implémenté dans l’environnement React assez facilement, mais l’inconvénient est que pour quelqu’un qui n’a jamais travaillé avec Rx dans aucun projet, le premier contact avec une approche réactive peut être un événement assez difficile.
React-RxJS rend React réactif. En ce sens, il peut utiliser des flux RxJS pour gérer l’état au niveau du domaine des applications React. Donc, si vous recherchez une solution de gestion d’état modulaire, performante et évolutive pour React, en particulier lorsque vous travaillez avec des API push, vous devriez essayer.