JavaScript de Cero a Experto / JS funcional
Inmutabilidad como estilo de vida
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:
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, INTACTACon 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,
- 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) implementadeshacer()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.