Une brève introduction à MobX
Bonjour !
On se retrouve aujourd’hui pour parler d’un outil assez puissant nommé MobX, que j'ai eu la chance de découvrir lors d’un projet client en React. Je vous propose aujourd’hui de le passer en revue ensemble.
MobX est une librairie de gestion de state (state management) framework-agnostic suivant un pattern de type observer pattern
. Ce pattern décrit des valeurs appelées Sujets où chaque sujet va pouvoir être observé par ce que le pattern appelle des Observers. Lorsque les sujets changent de valeurs, les observers sont notifiés et déclenchent les réactions adéquates.
Dans Mobx, les Subjets sont appelé observable
.
Les observables
Comme nous l’avons dit précédemment, les observables vont être la donnée que l’on va décider de traquer. Dans MobX, les observables vont représenter notre state.
Je précise ici que je ne parlerai que de l’utilisation que l’on peut faire de MobX avec des class. Mais c’est important de savoir que MobX fonctionne également de façon impérative, même si je trouve l’intérêt assez limité.
Ainsi, dans notre cas, chaque store MobX sera donc une class où ses attributs seront notre state et ses méthodes les fonctions qui nous permettent de le modifier ou de réagir à ces changements.
Afin de réagir à ces changements de states, nous devons donc marquer nos attributs de classes comme étant des observables
:
class TodoListStore { @observable items = []; }
Dans cet exemple, nous avons donc spécifié à MobX que l’attribut de class items
devra être traqué pour réagir à ses changements de valeurs.
Les dérivations
Les dérivations sont toutes valeurs produites en réaction à un changement de valeur d’observables
. Dans notre store, afin de produire des valeurs dérivées, nous utilisons le décorateur computed
.
Les valeurs computed
sont des fonctions qui ne prennent pas d’arguments et qui produisent une nouvelle valeur à chaque fois qu’un des observables
ou autres valeurs computed
utilisés dans la fonction changent. Dans une class MobX, les valeurs computed
sont présentées sous forme de getter.
Mobx précise également qu’une valeur computed se doit d'être une fonction pure, de ne pas mettre à jour le state ainsi que de ne pas déclencher d’autres effets de bord.
Les valeurs computed
se déclarent comme ceci:
class TodoListStore { @observable items = []; @computed get listLength() { return items.length } }
Dans cet exemple, nous déclarons une valeur computed
qui est un getter nommé listLength
. Cette valeur nous retourne le nombre d’items que compose notre todo-list. Cette valeur sera recalculée par Mobx lorsque la valeur d’items
changera.
Mobx indique que les valeurs computed
sont extrêmement optimisées et doivent être utilisées autant que possible. Comme déjà précisé, les computed values
ne seront pas recalculées si aucun des observables ou computed values
utilisés dans la fonction changent. Mobx traque également les différentes fonctions qui utilisent cette valeur computed
. S'il se trouve qu'aucune fonction ou composant ne l’utilise, MobX arrêtera de produire une nouvelle valeur en cas de changement et enverra les valeurs actuelles au garbage collector.
Les réactions
Les réactions sont similaires aux computed values
dans le sens où elles traquent des observables
et en cas de changement, appellera les fonctions concernées. La principale différence vient du fait que les réactions ne sont pas utilisés pour produire de nouvelles valeurs mais simplement pour déclencher des side-effects.
La façon la plus fréquente de créer des réactions est d’utiliser autorun
de MobX. Autorun fonctionne sur le même principe que computed
, il prend un callback en paramètre et ne l’appelle que lorsque l’un des observables
ou computed values
utilisés dans ce callback change :
class TodoListStore { @observable items = []; constructor() { autorun(() => console.log(`Items Size : this.listLength`)); } @computed get listLength() { return items.length } }
Le callBack passé à l’intérieur d’autorun sera ainsi exécuté une première fois lors de la création de la classe, puis à chaque fois que la valeur computed
listLength
changera.
Les Actions
Les actions sont les méthodes qui vont mettre à jour nos observables
ou plus globalement le state de notre store. Le concept d’action de MobX est assez similaire aux actions que l’on peut retrouver dans une architecture de type Flux.
Nous pouvons déclarer des actions comme ceci :
class TodoListStore { @observable items = []; constructor() { autorun(() => console.log(`Items Size : this.listLength`)); } @computed get listLength() { return items.length } @action addItem(newValue) { items.push(newValue); } }
Les actions sont de simples fonctions. Elles peuvent prendre autant de paramètres qu’elles le souhaitent. A noter ici que chaque appel à addItem
déclenchera automatiquement un nouveau calcul pour la valeur listLenght
et cela déclenchera également le callBack passé dans notre autorun
.
Il est important de noter également ici que si vous souhaitez utiliser des fonctions asynchrones dans des fonctions actions
, vous “briserez” le côté action
de la fonction une fois que vous entrerez dans un callBack ou une fois que vous aurez atteint le premier await
. Dans ce genre de conditions, une modification sur un observable
ne fonctionnera pas et déclenchera une erreur.
Ainsi ceci ne fonctionnera pas :
@action async addItem(newValueId) { const newValue = await myApi.getItemById(newValueId); items.push(newValue); }
Mais ceci fonctionnera:
async addItem(newValueId) { const newValue = await myApi.getItemById(newValueId); pushNewItem(newValue); } @action private pushNewItem(newItem) { items.push(newItem); }
ou encore :
async addItem(newValueId) { const newValue = await myApi.getItemById(newValueId); runInAction(() => { pushNewItem(newValue); }) }
Et voilà pour cette rapide introduction à MobX ! Dans le prochain article nous verrons comment utiliser ce que MobX a à nous offrir lorsqu’on l’utilise avec React !
Happy Coding !
Commentaires