Sélectionner une page
react rxjs

Qu’est-ce que RxJS ?

RxJS est une bibliothèque pour composer des programmes asynchrones et basés sur des événements en utilisant des séquences observables. Il fournit un type de base, l’ Observable , des types de satellites (Observer, Schedulers, Subjects) et des opérateurs inspirés des Array#extras (map, filter, reduce, every, etc.) pour permettre de gérer les événements asynchrones en tant que 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.

Où apprendre Rxjs ?

Le premier appel est bien sûr d’aller sur la page Web principale de RxJS :

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)

En ce qui concerne la combinaison de React avec Rx, il existe de nombreux articles que vous voudrez peut-être consulter :

Autres articles :

Pour quoi puis-je utiliser React 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, 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 ce n’est pas cool d’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-ce pas déjà comme la mise en œuvre du magasin 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 ( plus sur le sujet ) ;

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.

(Si les cartes Rx sont un peu déroutantes, allez ici) ou ici

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 les tests ?

Eh bien, c’est assez simple – juste pour tester un flux dans l’application de réaction, je pense que la meilleure approche serait de retirer notre tuyau du composant et de le conserver dans un fichier séparé avec d’autres opérateurs – puis tester les effets de chaque flux devient assez facile. Juste par exemple :

 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);
});

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.

Pour tout renseignement sur nos services d’agence digitale à Montpellier. Contactez-nous via le chat de notre site web du lundi au vendredi de 9h00 à 18h00

Demander un devis Solutions Développement I Solutions Design Graphique I Solutions Marketing Digital I Blog