CodeForge

JavaScript de Cero a Experto / JS funcional

Inmutabilidad como estilo de vida

Teoría20 min20 XP

Ya conoces las herramientas sueltas — spread, toSorted, map — y ya sufriste el bug de la referencia compartida en el módulo 5. Hoy las piezas se vuelven una REGLA: los datos no se modifican, se reemplazan por versiones nuevas. Suena costoso; paga en superpoderes concretos — comparar, deshacer y dormir tranquilo.

El libro del contador

Los cuatro patrones de siempre

Todo cambio de estado se reduce a cuatro operaciones — y las cuatro tienen su versión inmutable, con piezas que ya dominas:

Grábate el mapa mental: push/splice/sort MUTAN (prohibidas en este estilo); spread/filter/map/toSorted/toReversed PRODUCEN. El patrón 3 es el que más practica pide: map recorre, el ternario decide, y { ...g, valor: 6500 } fabrica el objeto editado sin tocar el original — tu "actualización inmutable" del módulo 5, ahora como reflejo.

Los pagos: por qué la industria adoptó esto

Detectar cambios cuesta UNA comparación. Si los datos nunca mutan, la pregunta "¿cambió algo?" es antes !== despues — comparar referencias, instantáneo. No hay que revisar campo por campo. Este es literalmente el motor de React: por eso su documentación te exige inmutabilidad, y por eso este módulo es la mejor preparación para ese supercurso.

El historial sale gratis. Como cada cambio es una versión nueva y las viejas quedan intactas, "deshacer" es guardar las versiones en un array y retroceder el índice:

deshacer.js
let historial = [gastos];             // versión 0

function aplicar(cambio) {
historial = [...historial, cambio(historial.at(-1))];
}

aplicar((g) => [...g, { nombre: "Taxi", valor: 15000 }]);
aplicar((lista) => lista.filter((g) => g.valor < 20000));

const deshacer = () => historial.at(-2);   // la versión anterior, INTACTA

Con datos mutables esto es imposible: todas las "versiones" del historial apuntarían al mismo objeto ya cambiado (el bug de referencias del módulo 5, en su forma más cruel).

Los sustos de referencia desaparecen. Nadie puede alterarte un array "por debajo" desde otra parte del código… porque nadie altera nada.

Nota honesta de rendimiento: copiar produce más objetos que mutar, sí — y para el 99% de las apps (cientos o miles de elementos) la diferencia es invisible, mientras que los bugs de mutación compartida son carísimos. Excepción legítima: mutación LOCAL — dentro de una función, sobre un array que acabas de crear tú y que aún no salió de ahí, un push en un bucle es privado e inofensivo. La regla dura es para datos que CIRCULAN. Y Object.freeze(obj) existe como cinturón (congela el primer nivel de un objeto), útil en desarrollo para cazar mutaciones traicioneras.

Guardas versiones para deshacer así: historial.push(gastos); gastos.push(nuevoGasto); historial.push(gastos); — y al deshacer, ¡la versión 'vieja' también tiene el gasto nuevo! ¿Por qué?

Mini-reto

El deshacer de "Mi Bolsillo": con el snippet del historial como base,

  1. aplica tres cambios (agregar Taxi, subir el Bus a 6500 con map+spread, quitar los mayores a 20000); 2) imprime historial.length (4 versiones) y comprueba que la versión 0 sigue TAL CUAL; 3) implementa deshacer() que retroceda una versión e imprime el estado tras deshacer dos veces. Acabas de construir la feature "Ctrl+Z" — con puro spread, map y filter.

Qué sigue

Funciones puras + datos inmutables = las piezas son de fiar. Ahora viene lo divertido: las piezas se COMBINAN. La próxima lección sube las funciones de categoría — funciones que reciben funciones y, sobre todo, funciones que FABRICAN funciones. Ya usaste mil (map, filter); ahora vas a crear las tuyas.