Qu’est-ce que MobX ?
MobX est une bibliothèque de gestion d’état réactive. Vous marquez vos données comme observables, et MobX suit tout ce qui les lit. Lorsque les données changent, les valeurs et les composants qui en dépendent se mettent à jour automatiquement. Il n’y a pas de sélecteurs à écrire ni d’abonnements à gérer.
La philosophie est presque à l’opposé de Redux. Là où Redux rend chaque modification explicite et traçable via des actions et des reducers, MobX rend les modifications implicites grâce au suivi automatique des dépendances. Vous écrivez du code orienté objet classique, et la réactivité s’opère en arrière-plan.
Observables et makeAutoObservable
La manière la plus simple de construire un store est d’utiliser une classe annotée avec makeAutoObservable, qui déduit ce qui doit être un observable, une valeur calculée (computed) ou une action.
// store.js
import { makeAutoObservable } from "mobx";
class TodoStore {
todos = [];
constructor() {
makeAutoObservable(this);
}
add(text) {
this.todos.push({ id: Date.now(), text, done: false });
}
toggle(id) {
const todo = this.todos.find((t) => t.id === id);
if (todo) todo.done = !todo.done;
}
get remaining() {
return this.todos.filter((t) => !t.done).length;
}
}
export const todoStore = new TodoStore();
makeAutoObservable traite les champs comme des observables, les getters comme des valeurs calculées et les méthodes comme des actions. Vous écrivez du JavaScript classique — this.todos.push(...) — et MobX s’occupe du suivi.
Valeurs calculées (Computed values)
Une valeur calculée dérive ses données d’observables. Elle est mise en cache et ne se recalcule que lorsqu’une de ses dépendances change.
// cart.js
class Cart {
items = [];
constructor() {
makeAutoObservable(this);
}
get total() {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
get isEmpty() {
return this.items.length === 0;
}
}
Comme total est calculée, la lire à cent endroits différents ne coûte qu’un seul calcul. Elle reste également cohérente : modifiez le prix d’un article et chaque lecteur verra le nouveau total sans aucune invalidation manuelle.
Actions
Les actions sont les fonctions qui modifient l’état. Le fait de les marquer explicitement permet à MobX de regrouper les mises à jour et de rendre les changements traçables.
// actions.js
import { action, makeObservable, observable } from "mobx";
class Settings {
theme = "dark";
fontSize = 16;
constructor() {
makeObservable(this, {
theme: observable,
fontSize: observable,
update: action,
});
}
update({ theme, fontSize }) {
if (theme) this.theme = theme;
if (fontSize) this.fontSize = fontSize;
}
}
Avec makeAutoObservable, vous bénéficiez de cela automatiquement. L’avantage des actions réside dans le regroupement (batching) : plusieurs modifications à l’intérieur d’une seule action produisent une seule réaction, et non une réaction par affectation. MobX peut également imposer que l’état ne soit modifié qu’à l’intérieur d’actions, ce qui permet de détecter les mutations accidentelles.
Connexion à React avec observer
Le composant d’ordre supérieur observer abonne un composant React aux observables qu’il lit lors du rendu.
// TodoList.jsx
import { observer } from "mobx-react-lite";
import { todoStore } from "./store";
export const TodoList = observer(() => {
return (
<div>
<p>{todoStore.remaining} remaining</p>
<ul>
{todoStore.todos.map((todo) => (
<li key={todo.id} onClick={() => todoStore.toggle(todo.id)}>
{todo.done ? "✓" : "○"} {todo.text}
</li>
))}
</ul>
<button onClick={() => todoStore.add("New task")}>Add</button>
</div>
);
});
Le composant ne liste jamais ses dépendances. MobX les enregistre au moment du rendu du composant, et le déclenche à nouveau dès que l’une d’elles change. Seuls les composants qui lisent réellement un observable modifié sont mis à jour, ce qui permet d’obtenir un granularité fine par construction.
Réactions et état asynchrone
Une réaction exécute un effet de bord lorsque les données observées changent, en dehors de l’arbre de rendu. Utilisez-les pour le logging, la persistance ou la synchronisation avec des systèmes externes.
// persist.js
import { autorun } from "mobx";
autorun(() => {
localStorage.setItem("theme", settings.theme);
});
Les actions asynchrones suivent le même schéma que n’importe quelle méthode de classe. MobX propose même flow pour les flux asynchrones basés sur les générateurs, mais une simple méthode async avec runInAction pour la mise à jour finale de l’état fonctionne très bien.
// users.js
import { makeAutoObservable, runInAction } from "mobx";
class UserStore {
users = [];
loading = false;
constructor() {
makeAutoObservable(this);
}
async fetchUsers() {
this.loading = true;
const res = await fetch("/api/users");
const users = await res.json();
runInAction(() => {
this.users = users;
this.loading = false;
});
}
}
Comparatif MobX
MobX, Redux et Zustand permettent tous de gérer l’état, mais leurs modèles mentaux diffèrent.
- MobX suit les dépendances automatiquement. Vous écrivez du code qui semble mutable et vous bénéficiez gratuitement de mises à jour granulaires.
- Redux rend les changements explicites via des actions et des reducers, ce qui offre une structure stricte et des devtools puissants.
- Zustand se situe entre les deux : un store minuscule avec des sélecteurs explicites et quasiment aucune cérémonie.
Si vous aimez les stores orientés objet et que vous détestez écrire des sélecteurs, MobX est un plaisir. Si vous préférez les mises à jour fonctionnelles et explicites ainsi qu’un arbre immuable unique, Redux est plus adapté. Si vous recherchez quelque chose de léger et prévisible, tournez-vous vers Zustand.
Bonnes pratiques
- Utilisez
makeAutoObservablepour les stores etobserverpour les composants. - Conservez les données dérivées dans des getters
computedau lieu de dupliquer les calculs. - Marquez les changements d’état comme des actions afin que les mises à jour soient regroupées et restent traçables.
- Gardez vos stores sous forme de classes simples et évitez d’y stocker l’état de l’UI non observable.
- Utilisez
runInActionpour les mises à jour d’état après unawait. - Conservez les données serveur dans une bibliothèque de data-fetching plutôt que dans un store MobX.
- Ne lisez pas d’observables en dehors d’une reaction ou d’un observer si vous attendez des mises à jour.
Erreurs courantes
- Oublier
observeret se demander pourquoi le composant ne se met pas à jour. - Lire un observable une seule fois et mettre la valeur en cache, perdant ainsi la réactivité.
- Stocker de gros objets non-observables et manquer les modifications.
- Muter l’état en dehors d’une action lorsque le strict mode est activé.
- Abuser des reactions pour des éléments qui devraient être calculés via computed.
- Utiliser MobX comme un cache d’état serveur.
Et après ?
MobX est une approche mature et automatique de l’état réactif. Comparez-la avec le modèle explicite de Redux Toolkit et le minimalisme de Zustand, et déportez vos données serveur vers TanStack Query. Ensuite, créez un petit store et constatez à quel point il y a peu de configuration pour maintenir l’UI synchronisée.