Une plongée profonde dans les avantages et les fonctionnalités de NgRx
Publié: 2022-03-11Si un chef d'équipe demande à un développeur d'écrire beaucoup de code passe-partout au lieu d'écrire quelques méthodes pour résoudre un certain problème, il a besoin d'arguments convaincants. Les ingénieurs logiciels sont des résolveurs de problèmes ; ils préfèrent automatiser les choses et éviter les passe-partout inutiles.
Même si NgRx est livré avec du code passe-partout, il fournit également de puissants outils de développement. Cet article démontre que passer un peu plus de temps à écrire du code apportera des avantages qui en valent la peine.
La plupart des développeurs ont commencé à utiliser la gestion d'état lorsque Dan Abramov a publié la bibliothèque Redux. Certains ont commencé à utiliser la gestion d'état parce que c'était une tendance, pas parce qu'ils en manquaient. Les développeurs utilisant un projet standard "Hello World" pour la gestion d'état pourraient rapidement se retrouver à écrire le même code encore et encore, augmentant la complexité sans gain.
Finalement, certains sont devenus frustrés et ont complètement abandonné la gestion de l'État.
Mon problème initial avec NgRx
Je pense que ce problème standard était un problème majeur avec NgRx. Au début, nous n'étions pas en mesure de voir la situation dans son ensemble. NgRx est une bibliothèque, pas un paradigme de programmation ou un état d'esprit. Cependant, afin de saisir pleinement la fonctionnalité et la convivialité de cette bibliothèque, nous devons élargir un peu plus nos connaissances et nous concentrer sur la programmation fonctionnelle. C'est à ce moment-là que vous pourriez écrire du code passe-partout et vous en sentir heureux. (Je le pense.) J'étais autrefois un sceptique NgRx; maintenant je suis un admirateur de NgRx.
Il y a quelque temps, j'ai commencé à utiliser la gestion d'état. J'ai vécu l'expérience passe-partout décrite ci-dessus, j'ai donc décidé d'arrêter d'utiliser la bibliothèque. Comme j'aime JavaScript, j'essaie d'acquérir au moins une connaissance fondamentale de tous les frameworks populaires utilisés aujourd'hui. Voici ce que j'ai appris en utilisant React.
React a une fonctionnalité appelée Hooks. Tout comme les composants dans Angular, les crochets sont des fonctions simples qui acceptent des arguments et des valeurs de retour. Un hook peut avoir un état, appelé effet secondaire. Ainsi, par exemple, un simple bouton dans Angular pourrait être traduit en React comme ceci :
@Component({ selector: 'simple-button', template: ` <button>Hello {{ name }}</button> `, }) export class SimpleButtonComponent { @Input() name!: string; } export default function SimpleButton(props: { name: string }) { return <button>{props.name} </button>; }Comme vous pouvez le voir, il s'agit d'une transformation simple :
- SimpleButtonComponent => SimpleButton
- @Input() nom => props.nom
- modèle => valeur de retour
Notre fonction React SimpleButton a une caractéristique importante dans le monde de la programmation fonctionnelle : c'est une fonction pure . Si vous lisez ceci, je suppose que vous avez entendu ce terme au moins une fois. NgRx.io cite deux fois les fonctions pures dans les concepts clés :
- Les changements d'état sont gérés par des fonctions pures appelées
reducersqui prennent l'état actuel et la dernière action pour calculer un nouvel état. - Les sélecteurs sont des fonctions pures utilisées pour sélectionner, dériver et composer des éléments d'état.
Dans React, les développeurs sont encouragés à utiliser les Hooks comme des fonctions pures autant que possible. Angular encourage également les développeurs à implémenter le même modèle en utilisant le paradigme Smart-Dumb Component.
C'est alors que j'ai réalisé qu'il me manquait certaines compétences cruciales en programmation fonctionnelle. Il n'a pas fallu longtemps pour comprendre NgRx, car après avoir appris les concepts clés de la programmation fonctionnelle, j'ai eu un "Aha ! moment » : j'avais amélioré ma compréhension de NgRx et je voulais l'utiliser davantage pour mieux comprendre les avantages qu'il offre.
Cet article partage mon expérience d'apprentissage et les connaissances que j'ai acquises sur NgRx et la programmation fonctionnelle. Je n'explique pas l'API pour NgRx ni comment appeler des actions ou utiliser des sélecteurs. Au lieu de cela, je partage pourquoi j'en suis venu à apprécier que NgRx est une excellente bibliothèque : ce n'est pas seulement une tendance relativement nouvelle, elle offre une multitude d'avantages.
Commençons par la programmation fonctionnelle .
Programmation fonctionnelle
La programmation fonctionnelle est un paradigme qui diffère grandement des autres paradigmes. Il s'agit d'un sujet très complexe avec de nombreuses définitions et directives. Cependant, la programmation fonctionnelle contient certains concepts de base et les connaître est une condition préalable à la maîtrise de NgRx (et de JavaScript en général).
Ces concepts de base sont :
- Fonction pure
- État immuable
- Effet secondaire
Je répète : c'est juste un paradigme, rien de plus. Il n'y a pas de bibliothèquefunctional.js que nous téléchargeons et utilisons pour écrire des logiciels fonctionnels. C'est juste une façon de penser à l'écriture d'applications. Commençons par le concept de base le plus important : la fonction pure .
Fonction pure
Une fonction est considérée comme une fonction pure si elle suit deux règles simples :
- Passer les mêmes arguments renvoie toujours la même valeur
- L'absence d'effet secondaire observable impliqué dans l'exécution de la fonction (un changement d'état externe, l'appel d'une opération d'E/S, etc.)
Ainsi, une fonction pure est simplement une fonction transparente qui accepte certains arguments (ou aucun argument) et renvoie une valeur attendue. Vous êtes assuré que l'appel de cette fonction n'entraînera pas d'effets secondaires, comme la mise en réseau ou la modification d'un état utilisateur global.
Prenons trois exemples simples :
//Pure function function add(a,b){ return a + b; } //Impure function breaking rule 1 function random(){ return Math.random(); } //Impure function breaking rule 2 function sayHello(name){ console.log("Hello " + name); }- La première fonction est pure car elle renverra toujours la même réponse en passant les mêmes arguments.
- La deuxième fonction n'est pas pure car elle est non déterministe et renvoie des réponses différentes à chaque fois qu'elle est appelée.
- La troisième fonction n'est pas pure car elle utilise un effet secondaire (appel
console.log).
Il est facile de discerner si la fonction est pure ou non. Pourquoi une fonction pure est-elle meilleure qu'une fonction impure ? Parce que c'est plus simple à penser. Imaginez que vous lisez du code source et que vous voyez un appel de fonction dont vous savez qu'il est pur. Si le nom de la fonction est correct, vous n'avez pas besoin de l'explorer ; vous savez que cela ne change rien, cela renvoie ce que vous attendez. C'est crucial pour le débogage lorsque vous avez une énorme application d'entreprise avec beaucoup de logique métier, car cela peut vous faire gagner un temps considérable.
De plus, c'est simple à tester. Vous n'avez pas besoin d'injecter quoi que ce soit ou de vous moquer de certaines fonctions à l'intérieur, vous passez simplement des arguments et testez si le résultat est une correspondance. Il existe un lien fort entre le test et la logique : si un composant est facile à tester, il est facile de comprendre comment et pourquoi il fonctionne.
Les fonctions pures sont accompagnées d'une fonctionnalité très pratique et conviviale appelée mémorisation. Si nous savons que l'appel des mêmes arguments renverra la même valeur, nous pouvons simplement mettre en cache les résultats et ne pas perdre de temps à l'appeler à nouveau. NgRx se situe définitivement au-dessus de la mémorisation ; c'est l'une des principales raisons pour lesquelles il est rapide.
Vous pouvez vous demander : « Qu'en est-il des effets secondaires ? Où vont-ils?" Dans sa conférence GOTO, Russ Olsen plaisante en disant que nos clients ne nous paient pas pour des fonctions pures, ils nous paient pour des effets secondaires. C'est vrai : personne ne se soucie de la fonction pure Calculatrice si elle n'est pas imprimée quelque part. Les effets secondaires ont leur place dans l'univers de la programmation fonctionnelle. Nous verrons cela sous peu.
Pour l'instant, passons à l'étape suivante dans la maintenance des architectures d'applications complexes, le prochain concept de base : immutable state .
État immuable
Il existe une définition simple pour un état immuable :
- Vous pouvez uniquement créer ou supprimer un état. Vous ne pouvez pas le mettre à jour.
En termes simples, pour mettre à jour l'âge d'un objet Utilisateur … :
let user = { username:"admin", age:28 }… vous devriez l'écrire comme ceci :
// Not like this newUser.age = 30; // But like this let newUser = {...user, age:29 }Chaque modification est un nouvel objet qui a copié les propriétés des anciens. En tant que tel, nous sommes déjà dans une forme d'état immuable.
String, Boolean et Number sont tous des états immuables : vous ne pouvez pas ajouter ou modifier des valeurs existantes. En revanche, une Date est un objet mutable : vous manipulez toujours le même objet date.
L'immuabilité s'applique à l'ensemble de l'application : si vous transmettez un objet utilisateur à l'intérieur de la fonction qui modifie son âge, il ne doit pas modifier un objet utilisateur, il doit créer un nouvel objet utilisateur avec un âge mis à jour et le renvoyer :
function updateAge(user, age) { return {...user, age: age) } let user = {username: 'admin', age: 29}; let newUser = updateAge(user, 32);Pourquoi devrions-nous y consacrer du temps et de l'attention ? Il y a quelques avantages qui méritent d'être soulignés.
L'un des avantages des langages de programmation back-end implique le traitement parallèle. Si un changement d'état ne dépend pas d'une référence et que chaque mise à jour est un nouvel objet, vous pouvez diviser le processus en morceaux et travailler sur la même tâche avec d'innombrables threads sans partager la même mémoire. Vous pouvez même paralléliser les tâches sur les serveurs.
Pour les frameworks tels que Angular et React, le traitement parallèle est l'un des moyens les plus avantageux d'améliorer les performances des applications. Par exemple, Angular doit vérifier les propriétés de chaque objet que vous transmettez via les liaisons d'entrée pour déterminer si un composant doit être rendu ou non. Mais si nous définissons ChangeDetectionStrategy.OnPush au lieu de la valeur par défaut, il vérifiera par référence et non par chaque propriété. Dans une grande application, cela permet de gagner du temps. Si nous mettons à jour notre état de manière immuable, nous obtenons cette amélioration des performances gratuitement.
L'autre avantage d'un état immuable que partagent tous les langages et frameworks de programmation est similaire aux avantages des fonctions pures : il est plus facile de réfléchir et de tester. Lorsqu'un changement est un nouvel état né d'un ancien, vous savez exactement sur quoi vous travaillez et vous pouvez suivre exactement comment et où l'état a changé. Vous ne perdez pas l'historique des mises à jour et vous pouvez annuler/rétablir les modifications de l'état (React DevTools en est un exemple).
Cependant, si un seul état est mis à jour, vous ne connaîtrez pas l'historique de ces modifications. Pensez à un état immuable comme l'historique des transactions d'un compte bancaire. C'est pratiquement un incontournable.
Maintenant que nous avons passé en revue l'immuabilité et la pureté, abordons le concept de base restant : effet secondaire .
Effet secondaire
On peut généraliser la définition d'un effet secondaire :
- En informatique, on dit qu'une opération, une fonction ou une expression a un effet secondaire si elle modifie une ou plusieurs valeurs de variable d'état en dehors de son environnement local. C'est-à-dire qu'il a un effet observable en plus de renvoyer une valeur (l'effet principal) à l'invocateur de l'opération.
En termes simples, tout ce qui modifie un état en dehors de la portée de la fonction (toutes les opérations d'E/S et certains travaux qui ne sont pas directement liés à la fonction) peut être considéré comme un effet secondaire. Cependant, nous devons éviter d'utiliser des effets secondaires à l'intérieur de fonctions pures car les effets secondaires contredisent la philosophie de la programmation fonctionnelle. Si vous utilisez une opération d'E/S à l'intérieur d'une fonction pure, elle cesse alors d'être une fonction pure.
Néanmoins, nous avons besoin d'avoir des effets secondaires quelque part, car une application sans eux serait inutile. Dans Angular, non seulement les fonctions pures doivent être protégées contre les effets secondaires, mais nous devons également éviter de les utiliser dans les composants et les directives.
Examinons comment nous pouvons implémenter la beauté de cette technique dans le framework Angular.
Programmation angulaire fonctionnelle
L'une des premières choses à comprendre à propos d'Angular est la nécessité de découpler les composants en composants plus petits aussi souvent que possible pour faciliter la maintenance et les tests. C'est nécessaire, car nous devons diviser notre logique métier. De plus, les développeurs Angular sont encouragés à laisser les composants uniquement à des fins de rendu et à déplacer toute la logique métier à l'intérieur des services.
Pour développer ces concepts, les utilisateurs d'Angular ont ajouté le modèle "Dumb-Smart Component" à leur vocabulaire. Ce modèle nécessite que les appels de service n'existent pas à l'intérieur des petits composants. Étant donné que la logique métier réside dans les services, nous devons toujours appeler ces méthodes de service, attendre leur réponse et ensuite seulement apporter des changements d'état. Ainsi, les composants ont une certaine logique comportementale à l'intérieur.
Pour éviter cela, nous pouvons créer un composant intelligent (composant racine), qui contient la logique métier et comportementale, transmettre les états via les propriétés d'entrée et appeler des actions écoutant les paramètres de sortie. De cette façon, les petits composants ne sont vraiment destinés qu'à des fins de rendu. Bien sûr, notre composant racine doit contenir des appels de service et nous ne pouvons pas simplement les supprimer, mais son utilité serait limitée à la logique métier uniquement, pas au rendu.
Regardons un exemple de composant de compteur. Un compteur est un composant qui a deux boutons qui augmentent ou diminuent la valeur, et un displayField qui affiche la currentValue . Nous nous retrouvons donc avec quatre composants :
- CompteurConteneur
- AugmenterBouton
- DiminuerBouton
- Valeur actuelle
Toute la logique vit à l'intérieur du CounterContainer , donc les trois ne sont que des moteurs de rendu. Voici le code pour les trois :
@Component({ selector: 'decrease-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Decrease </button>`, }) export class DecreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); } @Component({ selector: 'current-value', template: `<button> {{ currentValue }} </button>`, }) export class CurrentValueComponent { @Input() currentValue!: string; } @Component({ selector: 'increase-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Increase </button>`, }) export class IncreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); }Regardez comme ils sont simples et purs. Ils n'ont pas d'état ni d'effets secondaires, ils dépendent simplement des propriétés d'entrée et des événements d'émission. Imaginez comme il est facile de les tester. Nous pouvons les appeler des composants purs car c'est ce qu'ils sont vraiment. Ils dépendent uniquement des paramètres d'entrée, n'ont aucun effet secondaire et renvoient toujours la même valeur (chaîne de modèle) en transmettant les mêmes paramètres.

Ainsi, les fonctions pures de la programmation fonctionnelle sont transférées dans les composants purs d'Angular. Mais où va toute la logique ? La logique est toujours là mais à un endroit légèrement différent, à savoir le CounterComponent .
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { @Input() disabled!: boolean; currentValue = 0; get decreaseIsDisabled() { return this.currentValue === 0; } get increaseIsDisabled() { return this.currentValue === 100; } constructor() {} ngOnInit(): void {} decrease() { this.currentValue -= 1; } increase() { this.currentValue += 1; } } Comme vous pouvez le voir, la logique de comportement vit dans CounterContainer mais la partie rendu est manquante (elle déclare les composants à l'intérieur du modèle) car la partie rendu est destinée aux composants purs.
Nous pourrions injecter autant de service que nous le souhaitons car nous gérons ici toutes les manipulations de données et les changements d'état. Une chose à mentionner est que si nous avons un composant profondément imbriqué, nous ne devons pas créer un seul composant de niveau racine. Nous pouvons le diviser en composants intelligents plus petits et utiliser le même modèle. En fin de compte, cela dépend de la complexité et du niveau d'imbrication de chaque composant.
Nous pouvons facilement passer de ce modèle à la bibliothèque NgRx elle-même, qui se trouve juste une couche au-dessus.
Bibliothèque NgRx
Nous pouvons diviser n'importe quelle application Web en trois parties principales :
- Logique métier
- État de l'application
- Logique de rendu
La logique métier est tout le comportement qui se produit dans l'application, comme la mise en réseau, l'entrée, la sortie, l'API, etc.
État de l'application est l'état de l'application. Il peut être global, en tant qu'utilisateur actuellement autorisé, et également local, en tant que valeur actuelle du composant de compteur.
Rendu La logique englobe le rendu, comme l'affichage de données à l'aide de DOM, la création ou la suppression d'éléments, etc.
En utilisant le modèle Dumb-Smart, nous avons découplé la logique de rendu de la logique métier et de l'état de l'application, mais nous pouvons également les diviser car ils sont conceptuellement différents l'un de l'autre. L'état de l'application est comme un instantané de votre application à l'heure actuelle. Business Logic est comme une fonctionnalité statique qui est toujours présente dans votre application. La raison la plus importante de les diviser est que Business Logic est principalement un effet secondaire que nous voulons éviter autant que possible dans le code d'application. C'est à ce moment que la bibliothèque NgRx, avec son paradigme fonctionnel, brille.
Avec NgRx, vous découplez toutes ces parties. Il y a trois parties principales :
- Réducteurs
- Actions
- Sélecteurs
Combinés à la programmation fonctionnelle, les trois se combinent pour nous donner un outil puissant pour gérer des applications de toute taille. Examinons chacun d'eux.
Réducteurs
Un réducteur est une fonction pure, qui a une signature simple. Il prend un ancien état comme paramètre et renvoie un nouvel état, soit dérivé de l'ancien, soit d'un nouveau. L'état lui-même est un objet unique, qui vit avec le cycle de vie de votre application. C'est comme une balise HTML, un seul objet racine.
Vous ne pouvez pas modifier directement un objet d'état, vous devez le modifier avec des réducteurs. Cela a plusieurs avantages :
- La logique de changement d'état vit à un seul endroit, et vous savez où et comment l'état change.
- Les fonctions du réducteur sont des fonctions pures, faciles à tester et à gérer.
- Comme les réducteurs sont des fonctions pures, ils peuvent être mémorisés, ce qui permet de les mettre en cache et d'éviter des calculs supplémentaires.
- Les changements d'état sont immuables. Vous ne mettez jamais à jour la même instance. Au lieu de cela, vous en renvoyez toujours un nouveau. Cela permet une expérience de débogage de « voyage dans le temps ».
Voici un exemple trivial de réducteur :
function usernameReducer(oldState, username) { return {...oldState, username} }Même s'il s'agit d'un réducteur factice très simple, c'est le squelette de tous les réducteurs longs et complexes. Ils partagent tous les mêmes avantages. Nous pourrions avoir des centaines de réducteurs dans notre application et nous pouvons en fabriquer autant que nous le voulons.
Pour notre composant compteur, notre état et nos réducteurs pourraient ressembler à ceci :
interface State{ decreaseDisabled:boolean; increaseDisabled:boolean; currentValue:number; } const MIN_VALUE=0; const MAX_VALUE =100; function decreaseReducer(oldState) { const newValue = oldState.currentValue -1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MIN_VALUE } function increaseReducer(oldState) { const newValue = oldState.currentValue + 1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MAX_VALUE }Nous avons supprimé l'état du composant. Nous avons maintenant besoin d'un moyen de mettre à jour notre état et d'appeler le réducteur approprié. C'est alors que les actions entrent en jeu.
Actions
Une action est un moyen de notifier à NgRx d'appeler un réducteur et de mettre à jour l'état. Sans cela, il serait inutile d'utiliser NgRx. Une action est un objet simple que nous attachons au réducteur courant. Après l'avoir appelé, le réducteur approprié sera appelé, donc dans notre exemple, nous pourrions avoir les actions suivantes :
enum CounterActions { IncreaseValue = '[Counter Component] Increase Value', DecreaseValue = '[Counter Component] Decrease Value', } on(CounterActions.IncreaseValue,increaseReducer); on(CounterActions.DecreaseValue,decreaseReducer);Nos actions sont attachées aux réducteurs. Nous pouvons maintenant modifier davantage notre composant de conteneur et appeler les actions appropriées si nécessaire :
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }Remarque : nous avons supprimé l'état et nous le rajouterons sous peu .
Maintenant, notre CounterContainer n'a plus de logique de changement d'état. Il sait juste quoi expédier. Nous avons maintenant besoin d'un moyen d'afficher ces données dans la vue. C'est l'utilité des sélecteurs.
Sélecteurs
Un sélecteur est également une fonction pure très simple, mais contrairement à un réducteur, il ne met pas à jour l'état. Comme son nom l'indique, le sélecteur le sélectionne simplement. Dans notre exemple, nous pourrions avoir trois sélecteurs simples :
function selectCurrentValue(state) { return state.currentValue; } function selectDicreaseIsDisabled(state) { return state.decreaseDisabled; } function selectIncreaseIsDisabled(state) { return state.increaseDisabled; } En utilisant ces sélecteurs, nous pourrions sélectionner chaque tranche d'un état à l'intérieur de notre composant intelligent CounterContainer .
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="ecreaseIsDisabled$ | async" (decrease)="decrease()" > </decrease-button> <current-value [currentValue]="currentValue$ | async"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled$ | async" > </increase-button> `, }) export class CounterContainerComponent implements OnInit { decreaseIsDisabled$ = this.store.select(selectDicreaseIsDisabled); increaseIsDisabled$ = this.store.select(selectIncreaseIsDisabled); currentValue$ = this.store.select(selectCurrentValue); constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }Ces sélections sont asynchrones par défaut (comme le sont les Observables en général). Cela n'a aucune importance, du moins du point de vue du modèle. La même chose serait vraie pour un synchrone, car nous sélectionnerions simplement quelque chose de notre état.
Prenons du recul et examinons la situation dans son ensemble pour voir ce que nous avons accompli jusqu'à présent. Nous avons une application Counter, qui comporte trois parties principales qui sont presque découplées les unes des autres. Personne ne sait comment l'état de l'application se gère ou comment la couche de rendu rend l'état.
Les parties découplées utilisent le pont (Actions, Sélecteurs) pour se connecter les unes aux autres. Ils sont découplés à tel point qu'on pourrait prendre tout le code de State Application et le déplacer vers un autre projet, comme pour une version mobile par exemple. La seule chose que nous aurions à mettre en œuvre serait le rendu. Mais qu'en est-il des tests ?
À mon humble avis, les tests sont la meilleure partie de NgRx. Tester cet exemple de projet revient à jouer au tic-tac-toe. Il n'y a que des fonctions pures et des composants purs, donc les tester est un jeu d'enfant. Imaginez maintenant si ce projet devient plus grand, avec des centaines de composants. Si nous suivons le même schéma, nous ajouterions simplement de plus en plus de pièces ensemble. Cela ne deviendrait pas une masse de code source désordonnée et illisible.
Nous avons presque terminé. Il ne reste plus qu'une chose importante à couvrir : les effets secondaires. J'ai mentionné les effets secondaires à plusieurs reprises jusqu'à présent, mais je n'ai pas expliqué où les stocker.
En effet, les effets secondaires sont la cerise sur le gâteau et en créant ce modèle, il est très facile de les supprimer du code de l'application.
Effets secondaires
Disons que notre application de compteur contient une minuterie et toutes les trois secondes, elle augmente automatiquement la valeur de un. Il s'agit d'un effet secondaire simple, qui doit vivre quelque part. C'est le même effet secondaire, par définition, qu'une requête Ajax.
Si l'on pense aux effets secondaires, la plupart ont deux raisons principales d'exister :
- Faire quoi que ce soit en dehors de l'environnement de l'État
- Mise à jour de l'état de l'application
Par exemple, stocker un état dans le LocalStorage est la première option, tandis que la mise à jour de l'état à partir de la réponse Ajax est la seconde. Mais ils partagent tous les deux la même signature : chaque effet secondaire doit avoir un point de départ. Il doit être appelé au moins une fois pour l'inviter à démarrer l'action.
Comme nous l'avons souligné précédemment, NgRx dispose d'un bel outil pour donner une commande à quelqu'un. C'est une action. Nous pourrions appeler n'importe quel effet secondaire en envoyant une action. Le pseudo code pourrait ressembler à ceci :
function startTimer(){ setInterval(()=>{ console.log("Hello application"); },3000) } on(CounterActions.StartTime,startTimer) ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);C'est assez banal. Comme je l'ai mentionné plus tôt, les effets secondaires mettent à jour quelque chose ou non. Si un effet secondaire ne met rien à jour, il n'y a rien à faire ; nous le laissons juste. Mais si on veut mettre à jour un état, comment fait-on ? De la même manière qu'un composant essaie de mettre à jour un état : en appelant une autre action. Nous appelons donc une action à l'intérieur de l'effet secondaire, qui met à jour l'état :
function startTimer(store) { setInterval(()=> { // We are dispatching another action dispatch(CounterActions.IncreaseValue) }, 3000) } on(CounterActions.StartTime, startTimer); ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);Nous avons maintenant une application entièrement fonctionnelle.
Résumant notre expérience NgRx
Il y a quelques sujets importants que je voudrais mentionner avant de terminer notre voyage NgRx :
- Le code affiché est un simple pseudo-code que j'ai inventé pour l'article ; il n'est adapté qu'à des fins de démonstration. NgRx est l'endroit où vivent les vraies sources.
- Il n'y a pas de directive officielle qui prouve ma théorie sur la connexion de la programmation fonctionnelle avec la bibliothèque NgRx. C'est juste mon opinion formée après avoir lu des dizaines d'articles et d'échantillons de code source créés par des personnes hautement qualifiées.
- Après avoir utilisé NgRx, vous vous rendrez certainement compte que c'est bien plus complexe que cet exemple simple. Mon objectif n'était pas de le rendre plus simple qu'il ne l'est en réalité, mais de vous montrer que même si c'est un peu complexe et peut même entraîner un chemin plus long vers la destination, cela en vaut la peine.
- La pire utilisation de NgRx est de l'utiliser partout, quelle que soit la taille ou la complexité de l'application. Il y a certains cas dans lesquels vous ne devriez pas utiliser NgRx ; par exemple, dans les formulaires. Il est presque impossible d'implémenter des formulaires dans NgRx. Les formulaires sont collés au DOM lui-même ; ils ne peuvent pas vivre séparément. Si vous essayez de les découpler, vous vous retrouverez à détester non seulement NgRx mais la technologie Web en général.
- Parfois, utiliser le même code passe-partout, même pour un petit exemple, peut se transformer en cauchemar, même si cela peut nous être bénéfique à l'avenir. Si tel est le cas, intégrez simplement une autre bibliothèque étonnante, qui fait partie de l'écosystème NgRx (ComponentStore).
