Loupe

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 !

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus