L'état immuable en toute simplicité : Comprendre Immer
Mettre à jour un état imbriqué en JavaScript sans mutation est fastidieux. Vous devez décomposer les objets à chaque niveau, suivre les références qui ont changé, et espérer ne pas avoir accidentellement muté quelque chose en cours de route. Pour une simple mise à jour imbriquée, vous pourriez écrire cinq lignes de logique de copie minutieuse.
Immer résout ce problème avec une approche différente : écrire du code qui ressemble à de la mutation, mais produit automatiquement un état immuable. Cet article explique comment fonctionne l’immuabilité basée sur les proxies d’Immer, pourquoi Redux Toolkit l’utilise en interne, et quelles précautions pratiques vous devriez connaître avant de l’adopter.
Points clés à retenir
- Immer vous permet d’écrire du code de style mutation qui produit un état immuable grâce à sa fonction
produceet aux brouillons basés sur les proxies - Le partage structurel garantit que seules les branches modifiées obtiennent de nouvelles références, préservant ainsi les performances pour les vérifications de re-rendu de React et Redux
- Redux Toolkit utilise Immer en interne, donc les reducers
createSlicegèrent automatiquement l’immuabilité sans imports supplémentaires - Attention aux pièges courants : ne réassignez pas le brouillon lui-même, évitez de mélanger les mutations de brouillon avec les valeurs de retour, et soyez prudent avec les instances de classe
Comment Immer produit un état immuable
L’API principale d’Immer est la fonction produce. Vous lui passez votre état actuel et une fonction « recette ». À l’intérieur de cette recette, vous recevez un draft (brouillon) — un proxy enveloppant votre état original. Vous modifiez le brouillon en utilisant des mutations JavaScript normales. Lorsque la recette se termine, Immer génère un nouvel état immuable reflétant vos modifications.
import { produce } from "immer"
const baseState = {
user: { name: "Alice", settings: { theme: "dark" } }
}
const nextState = produce(baseState, draft => {
draft.user.settings.theme = "light"
})
// baseState.user.settings.theme est toujours "dark"
// nextState.user.settings.theme est "light"
Le modèle mental est simple : faites comme si vous mutiez, mais Immer gère les mises à jour immuables en coulisses.
Immuabilité basée sur les proxies et partage structurel
Immer utilise les objets Proxy de JavaScript pour intercepter vos lectures et écritures sur le brouillon. Lorsque vous accédez à une propriété imbriquée, Immer crée paresseusement un proxy pour ce chemin. Lorsque vous écrivez dans une propriété, Immer marque ce nœud (et ses ancêtres) comme modifié et crée des copies superficielles uniquement là où c’est nécessaire.
Cette approche de copie à l’écriture permet le partage structurel. Les portions inchangées de votre arbre d’état conservent les mêmes références d’objet. Seules les branches modifiées obtiennent de nouvelles références. Cela compte pour React et Redux car les vérifications d’égalité de référence déterminent si les composants doivent être re-rendus.
Immer moderne (v10+) nécessite le support natif de Proxy — il n’y a pas de solution de repli ES5. Cela convient aux navigateurs actuels et à Node.js, mais mérite d’être noté si vous ciblez des environnements inhabituels.
Intégration d’Immer dans Redux Toolkit
Si vous utilisez Redux Toolkit, vous utilisez déjà Immer. Le createSlice de RTK enveloppe automatiquement votre logique de reducer avec produce. Vous écrivez du code « mutant » dans les case reducers, et RTK gère l’immuabilité :
import { createSlice } from "@reduxjs/toolkit"
const todosSlice = createSlice({
name: "todos",
initialState: [],
reducers: {
addTodo: (state, action) => {
state.push(action.payload) // Ressemble à une mutation, mais c'est sûr
},
toggleTodo: (state, action) => {
const todo = state.find(t => t.id === action.payload)
if (todo) todo.completed = !todo.completed
}
}
})
Cette intégration d’Immer dans Redux Toolkit élimine le code répétitif lié à l’opérateur de décomposition qui rendait les reducers Redux vanilla verbeux. Vous n’avez pas besoin d’importer produce séparément — RTK le configure pour vous.
Discover how at OpenReplay.com.
Gel automatique et comportement d’itération
Immer gèle automatiquement l’état produit par défaut (en utilisant Object.freeze). Cela détecte les mutations accidentelles pendant le développement mais ajoute une surcharge. Vous pouvez le désactiver en production via la configuration si le profilage montre que cela compte :
import { setAutoFreeze } from "immer"
setAutoFreeze(false) // Désactiver en production pour les performances
Immer moderne utilise par défaut l’itération souple pour les performances : seules les clés de chaîne énumérables sont parcourues dans les brouillons. C’est généralement ce que vous voulez pour l’état de l’application, mais cela signifie que les clés symboles et les propriétés non énumérables sont ignorées sauf si vous activez explicitement l’itération stricte. Cela importe principalement dans les cas limites ou la manipulation de données de bas niveau.
Précautions pratiques pour les mises à jour immuables en JavaScript
Plusieurs pièges trompent les développeurs nouveaux à Immer :
Ne réassignez pas le brouillon lui-même. Modifier draft.property fonctionne. Réassigner draft = newValue ne fait rien d’utile car vous ne faites que changer la liaison du paramètre local.
Retourner des valeurs compte. Si votre recette retourne une valeur, Immer l’utilise comme nouvel état au lieu du brouillon modifié. Retourner undefined est traité de la même manière que ne rien retourner, mais mélanger les mutations de brouillon et les retours explicites est une source courante de bugs — utilisez une approche ou l’autre.
Les classes et objets exotiques nécessitent de la prudence. Immer fonctionne mieux avec les objets simples, les tableaux, les Maps et les Sets. Les instances de classe ne sont pas automatiquement proxifiées correctement. Vous devrez peut-être les marquer comme immuables ou les gérer séparément (comme indiqué dans la documentation officielle sur les pièges).
Les performances ne sont pas gratuites. Immer convient à l’état UI typique — formulaires, listes, préférences utilisateur. Pour les boucles intensives traitant des milliers d’éléments par frame, mesurez avant de supposer qu’Immer n’ajoute aucune surcharge. La machinerie de proxy a un coût.
L’état en forme d’arbre fonctionne mieux. Immer suppose que votre état est un arbre. Les références circulaires ou les références d’objets partagées entre les branches peuvent produire des résultats inattendus.
Conclusion
Immer excelle pour les mises à jour d’état imbriqué modérément complexes — exactement ce que vous rencontrez dans les composants React et les reducers Redux. Il élimine le code répétitif, détecte les mutations accidentelles et s’intègre parfaitement avec Redux Toolkit.
Pour un état plat simple, les opérateurs de décomposition natifs fonctionnent bien. Pour le traitement de données critique en termes de performances, effectuez d’abord des benchmarks. Mais pour la gestion d’état frontend typique, l’approche basée sur les proxies d’Immer offre le meilleur équilibre entre expérience développeur et exactitude.
FAQ
Oui. Vous pouvez envelopper vos mises à jour d'état avec produce directement, ou utiliser le hook useImmer d'Immer du package use-immer. Cela vous donne la même syntaxe de style mutation pour l'état local des composants que Redux Toolkit fournit pour l'état global.
Immer a un excellent support TypeScript. La fonction produce infère automatiquement les types à partir de votre état de base, et les objets brouillons maintiennent un typage approprié. Vous obtenez une autocomplétion complète et une vérification de type tout en écrivant du code de style mutation.
Immer supporte nativement les Maps et Sets. Vous pouvez utiliser les méthodes standard Map et Set comme set, delete et add directement sur les brouillons dans les versions modernes sans aucune configuration supplémentaire.
Seulement si le profilage montre que cela cause des problèmes de performance. Le gel automatique aide à détecter les bugs en lançant des erreurs sur les mutations accidentelles. Pour la plupart des applications, la surcharge est négligeable, mais les mises à jour à haute fréquence peuvent bénéficier de sa désactivation via setAutoFreeze false.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.