Estado Inmutable de Forma Sencilla: Entendiendo Immer
Actualizar estado anidado en JavaScript sin mutación es tedioso. Debes expandir objetos en cada nivel, rastrear qué referencias cambiaron y esperar no haber mutado algo accidentalmente en el proceso. Para una simple actualización anidada, podrías escribir cinco líneas de lógica de copiado cuidadosa.
Immer resuelve este problema con un enfoque diferente: escribe código que parece mutación, pero produce estado inmutable automáticamente. Este artículo explica cómo funciona la inmutabilidad basada en proxies de Immer, por qué Redux Toolkit lo usa internamente, y qué advertencias prácticas debes conocer antes de adoptarlo.
Puntos Clave
- Immer te permite escribir código estilo mutación que produce estado inmutable a través de su función
producey borradores basados en proxies - El compartir estructural asegura que solo las ramas modificadas obtengan nuevas referencias, preservando el rendimiento para las verificaciones de re-renderizado de React y Redux
- Redux Toolkit usa Immer internamente, por lo que los reducers de
createSlicemanejan automáticamente la inmutabilidad sin importaciones adicionales - Ten cuidado con errores comunes: no reasignes el borrador en sí mismo, evita mezclar mutaciones del borrador con valores de retorno, y sé cauteloso con instancias de clases
Cómo Immer Produce Estado Inmutable
El API central de Immer es la función produce. Le pasas tu estado actual y una función “receta”. Dentro de esa receta, recibes un draft (borrador)—un proxy que envuelve tu estado original. Modificas el borrador usando mutaciones normales de JavaScript. Cuando la receta termina, Immer genera un nuevo estado inmutable que refleja tus cambios.
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 sigue siendo "dark"
// nextState.user.settings.theme es "light"
El modelo mental es directo: pretende que estás mutando, pero Immer maneja las actualizaciones inmutables detrás de escena.
Inmutabilidad Basada en Proxies y Compartir Estructural
Immer usa objetos Proxy de JavaScript para interceptar tus lecturas y escrituras en el borrador. Cuando accedes a una propiedad anidada, Immer crea perezosamente un proxy para esa ruta. Cuando escribes en una propiedad, Immer marca ese nodo (y sus ancestros) como modificado y crea copias superficiales solo donde sea necesario.
Este enfoque de copia en escritura habilita el compartir estructural. Las porciones sin cambios de tu árbol de estado permanecen con las mismas referencias de objeto. Solo las ramas modificadas obtienen nuevas referencias. Esto importa para React y Redux porque las verificaciones de igualdad de referencias determinan si los componentes se re-renderizan.
Immer moderno (v10+) requiere soporte nativo de Proxy—no hay respaldo para ES5. Esto está bien para navegadores actuales y Node.js, pero vale la pena notarlo si apuntas a entornos inusuales.
Integración de Immer en Redux Toolkit
Si usas Redux Toolkit, ya estás usando Immer. El createSlice de RTK envuelve tu lógica de reducer con produce automáticamente. Escribes código “mutante” en los case reducers, y RTK maneja la inmutabilidad:
import { createSlice } from "@reduxjs/toolkit"
const todosSlice = createSlice({
name: "todos",
initialState: [],
reducers: {
addTodo: (state, action) => {
state.push(action.payload) // Parece mutación, pero es seguro
},
toggleTodo: (state, action) => {
const todo = state.find(t => t.id === action.payload)
if (todo) todo.completed = !todo.completed
}
}
})
Esta integración de Immer en Redux Toolkit elimina el código repetitivo del operador spread que hacía verbosos los reducers de Redux vanilla. No necesitas importar produce por separado—RTK lo configura por ti.
Discover how at OpenReplay.com.
Auto-Congelamiento y Comportamiento de Iteración
Immer auto-congela el estado producido por defecto (usando Object.freeze). Esto detecta mutaciones accidentales durante el desarrollo pero añade sobrecarga. Puedes deshabilitarlo en producción mediante configuración si el perfilado muestra que importa:
import { setAutoFreeze } from "immer"
setAutoFreeze(false) // Deshabilitar en producción para rendimiento
Immer moderno usa por defecto iteración flexible para rendimiento: solo las claves de cadena enumerables se iteran en los borradores. Esto es usualmente lo que quieres para el estado de aplicación, pero significa que las claves de símbolos y propiedades no enumerables se omiten a menos que habilites explícitamente la iteración estricta. Esto importa principalmente en casos extremos o manipulación de datos de bajo nivel.
Advertencias Prácticas para Actualizaciones Inmutables en JavaScript
Varios errores sorprenden a desarrolladores nuevos en Immer:
No reasignes el borrador en sí mismo. Modificar draft.property funciona. Reasignar draft = newValue no hace nada útil porque solo estás cambiando el enlace del parámetro local.
Retornar valores importa. Si tu receta retorna un valor, Immer usa eso como el nuevo estado en lugar del borrador modificado. Retornar undefined se trata igual que no retornar nada, pero mezclar mutaciones del borrador y retornos explícitos es una fuente común de bugs—usa un enfoque u otro.
Las clases y objetos exóticos necesitan cuidado. Immer funciona mejor con objetos planos, arrays, Maps y Sets. Las instancias de clases no se proxean automáticamente correctamente. Puede que necesites marcarlas como inmutables o manejarlas por separado (como se describe en la documentación oficial de errores).
El rendimiento no es gratis. Immer es adecuado para estado típico de UI—formularios, listas, preferencias de usuario. Para bucles intensivos procesando miles de elementos por frame, mide antes de asumir que Immer no añade sobrecarga. La maquinaria de proxies tiene un costo.
El estado en forma de árbol funciona mejor. Immer asume que tu estado es un árbol. Referencias circulares o referencias de objetos compartidas entre ramas pueden producir resultados inesperados.
Conclusión
Immer brilla para actualizaciones de estado anidado moderadamente complejas—exactamente lo que encuentras en componentes de React y reducers de Redux. Elimina código repetitivo, detecta mutaciones accidentales, e integra perfectamente con Redux Toolkit.
Para estado plano simple, los operadores spread nativos funcionan bien. Para procesamiento de datos crítico en rendimiento, haz benchmarks primero. Pero para gestión de estado frontend típica, el enfoque basado en proxies de Immer ofrece el mejor balance entre experiencia de desarrollo y corrección.
Preguntas Frecuentes
Sí. Puedes envolver tus actualizaciones de estado con produce directamente, o usar el hook useImmer de Immer del paquete use-immer. Esto te da la misma sintaxis estilo mutación para estado local de componente que Redux Toolkit proporciona para estado global.
Immer tiene excelente soporte para TypeScript. La función produce infiere tipos automáticamente desde tu estado base, y los objetos borrador mantienen el tipado apropiado. Obtienes autocompletado completo y verificación de tipos mientras escribes código estilo mutación.
Immer soporta Maps y Sets nativamente. Puedes usar métodos estándar de Map y Set como set, delete y add directamente en borradores en versiones modernas sin ninguna configuración adicional.
Solo si el perfilado muestra que causa problemas de rendimiento. Auto-freeze ayuda a detectar bugs lanzando errores en mutaciones accidentales. Para la mayoría de aplicaciones la sobrecarga es insignificante, pero actualizaciones de alta frecuencia pueden beneficiarse de deshabilitarlo mediante 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.