Por qué Redux ganó sobre el Flux original
Redux venció al Flux original por sustraccion y por pureza. Colapso los muchos stores en uno solo, eliminando de golpe el problema de ordenacion que waitFor apenas contenia; hizo trivial el dispatcher hasta convertirlo en un simple metodo; y sobre todo hizo puros los reducers, funciones de estado y action a nuevo estado sin mutacion ni efectos. Esta leccion explica esas tres decisiones, muestra por que la pureza fue la palanca que habilito el tooling legendario de Redux, y cierra con la coda de 2026: Redux perdio terreno como libreria pero la idea del reducer es hoy aire que respiramos.
Cuando Dan Abramov y Andrew Clark presentaron Redux en 2015, no añadieron nada a Flux: le quitaron cosas. Donde Flux tenía muchos stores, Redux dejó uno; donde Flux tenía un dispatcher con mecanismos de ordenación, Redux dejó un método casi vacío; donde los stores de Flux mutaban objetos, Redux exigió funciones puras. El resultado fue una herramienta más pequeña que su antecesora y, precisamente por serlo, mucho más poderosa. Redux ganó la guerra de las implementaciones de Flux no por hacer más, sino por hacer menos con más disciplina, y la recompensa de esa disciplina fue un ecosistema de herramientas que ninguna alternativa flexible pudo igualar. Esta lección disecciona sus tres decisiones y extrae la moraleja.
- Explicar por qué un único store elimina el problema de ordenación entre stores de Flux.
- Ver cómo Redux reduce el dispatcher a un método trivial y elimina
waitFor. - Definir el reducer como función pura
(estado, action) => nuevoEstadoy su herencia de Elm. - Conectar la pureza del reducer con el tooling —time-travel, serialización— que hizo ganar a Redux.
Un solo store: el árbol único
La primera y más radical decisión de Redux fue tener un único store para toda la aplicación. Todo el estado vive en un solo objeto, un árbol, en lugar de repartirse entre muchos stores independientes como en Flux. Esto parece una limitación —¿no se vuelve inmanejable?— pero resuelve de un plumazo el problema más espinoso de Flux: las dependencias entre stores.
Recuerda el waitFor de la lección dos, con el que un store esperaba a que otro procesara la action antes de reaccionar. Ese mecanismo existía porque había varios stores y el orden entre ellos importaba. Con un solo store, no hay varios stores que ordenar, así que la pregunta “¿quién procesa primero?” simplemente desaparece. Redux no resolvió el problema de la ordenación: lo disolvió eliminando la condición que lo hacía posible.
Esa centralización es a la vez la mayor fuerza de Redux y la razón de que existan alternativas más ligeras. Un árbol único es un mirador perfecto —todo el estado en un punto, inspeccionable de golpe— pero también una aduana por la que ha de pasar hasta el cambio más trivial. Para una aplicación grande con estado muy interconectado la aduana compensa; para un contador local o un formulario aislado es burocracia, y ahí es donde en 2026 brillan Zustand o el propio useState. Redux acertó con la idea del árbol único, pero el ecosistema aprendió después que no todo estado merece vivir en él, y esa lección es la que reparte hoy el trabajo entre Redux y sus primos ligeros.
Adiós al dispatcher y al waitFor
En Flux, el dispatcher era una pieza propia, un singleton con lógica de registro y de espera. En Redux, el dispatcher se encoge hasta ser un método del store, dispatch, cuyo trabajo es tan simple que casi da vergüenza: toma la action, se la pasa al reducer junto al estado actual, guarda lo que el reducer devuelve y avisa a los suscriptores.
// el corazon de Redux cabe en unas lineas: dispatch es casi trivial
function createStore(reducer) {
let estado = reducer(undefined, { type: "@@INIT" });
const oyentes = [];
return {
getState: () => estado,
dispatch(accion) {
estado = reducer(estado, accion); // toda la logica esta en el reducer
oyentes.forEach((oyente) => oyente());
return accion;
},
subscribe(oyente) {
oyentes.push(oyente);
return () => oyentes.splice(oyentes.indexOf(oyente), 1);
},
};
}
No hay register, no hay tokens, no hay waitFor. Toda la lógica que en Flux se repartía entre el dispatcher y los stores se concentra ahora en una única función, el reducer, que recibe el estado entero y devuelve el estado entero. El dispatcher dejó de ser una pieza para volverse un detalle de una línea.
Merece la pena detenerse en lo que este colapso significa conceptualmente. En Flux, el dispatcher era el sujeto activo —repartía, coordinaba, esperaba—; en Redux, el verbo se traslada del dispatcher al reducer, y el dispatcher queda reducido a pura fontanería. La inteligencia deja de estar en cómo se reparten los hechos y pasa a estar en cómo se interpretan, que es exactamente donde debe vivir la lógica de una aplicación. Flux distribuía la responsabilidad entre dos piezas difusas; Redux la concentra en una sola, nítida y comprobable.
Reducers puros: el corazón heredado de Elm
Aquí está la joya que Redux tomó de la Elm Architecture. Un reducer es una función pura con la firma (estado, action) => nuevoEstado: recibe el estado actual y una action, y devuelve el estado nuevo, sin mutar el viejo y sin efectos secundarios. La misma entrada produce siempre la misma salida.
// un reducer puro: sin mutacion, sin efectos, sin sorpresas
function contador(estado = 0, accion) {
switch (accion.type) {
case "INCREMENTAR":
return estado + 1;
case "DECREMENTAR":
return estado - 1;
default:
return estado; // ante lo desconocido, devuelve el estado intacto
}
}
Como el estado global es un solo árbol, Redux lo compone a partir de reducers pequeños con combineReducers, cada uno dueño de una rama. La pureza es la regla inviolable: un reducer que llama a una API, que lee la hora, que muta su argumento o que genera un identificador aleatorio deja de ser puro y rompe todas las garantías. Los efectos viven fuera, en middleware.
import { combineReducers } from "redux";
// el arbol unico se ensambla desde reducers puros por rama
const reducerRaiz = combineReducers({ contador, tareas, sesion });
Esa composición es la contrapartida de haber renunciado a los múltiples stores: no pierdes la modularidad, la recuperas como funciones puras que se combinan en un árbol, en lugar de como objetos con estado que se coordinan a mano con waitFor. Y como el reducer es total —contesta a toda action, aunque sea devolviendo el estado intacto en el default—, nunca existe un hecho que deje el sistema en un limbo indefinido: cada action tiene una respuesta definida sobre cada rama del árbol, lo que elimina de raíz otra familia de estados imposibles.
Es tentador colar un efecto en un reducer —un fetch, un log, un Date.now— porque funciona en la ejecución normal. Pero cada impureza rompe justo las propiedades por las que elegiste Redux: si el reducer no es determinista, no puedes reproducir sesiones ni viajar en el tiempo; si muta su argumento, el DevTools muestra estados falsos. La pureza no es estilo: es la precondición de la que cuelga todo el resto. Los efectos van en middleware como thunk o saga, nunca en el reducer.
Por qué ganó la pureza: el tooling
Las tres decisiones anteriores convergen en una sola recompensa, y es la que de verdad ganó la guerra. Como el estado es un único valor serializable y solo cambia por reducers puros aplicados a actions que son datos, todo el historial de la aplicación se vuelve inspeccionable y reproducible. Y sobre esa base, Redux construyó un tooling que dejó a la competencia sin argumentos.
Time-travel debugging
Guardando la lista de actions y el estado inicial, el DevTools reconstruye cualquier instante: avanza, retrocede, salta a un estado pasado. Solo es posible porque los reducers son puros.
Estado serializable
Un único árbol de datos planos se guarda en disco, se manda con un reporte de bug y se recarga tal cual. El bug del usuario llega a tu máquina reproducido.
Hot reloading de lógica
Como la lógica vive en funciones puras separadas del estado, se puede recargar el reducer sin perder el estado actual. Editas la regla y la app sigue donde estaba.
Tests sin montar nada
Probar un reducer es llamar a una función y comparar el resultado. Sin DOM, sin mocks, sin ciclo de vida. La pureza hace el test trivial.
Ninguna de estas capacidades era alcanzable con los stores mutables y llenos de efectos del Flux original. Redux no ganó por marketing ni por ergonomía —de hecho, su verbosidad era legendaria y motivó Redux Toolkit—; ganó porque la disciplina de la pureza desbloqueó herramientas que la flexibilidad jamás habría permitido.
Hubo además un efecto de red que amplificó todo lo anterior: cuanto más se usaba Redux, más middleware, más extensiones del DevTools y más patrones documentados aparecían, y cada adición hacía a Redux un poco más inevitable para el siguiente equipo que decidía. La pureza fue la semilla, pero el ecosistema que creció sobre ella —thunk y saga para los efectos, reselect para las derivaciones, y más tarde Redux Toolkit para domar la verbosidad— fue lo que convirtió a Redux en la respuesta por defecto durante casi una década.
No conviene subestimar cuánto pesó el Redux DevTools en la adopción. Ver la lista de actions desfilando, saltar a cualquier estado pasado y observar el árbol completo en cada instante convirtió una promesa abstracta —la predecibilidad— en algo tangible que cabía en un GIF de diez segundos. Muchas herramientas técnicamente superiores han perdido frente a otras peores que sí supieron hacer visible su ventaja. La disciplina de la pureza le dio a Redux una ventaja real; el DevTools la hizo evidente para todos.
El Redux verboso de 2015 —actions a mano, constantes de type, switch interminables— es hoy legacy. El estándar es Redux Toolkit con createSlice, que genera actions y reducer de una tacada y usa Immer por debajo para que escribas mutaciones aparentes que siguen siendo puras. Y para estado de servidor, RTK Query. La idea es idéntica; la ceremonia, una fracción.
import { createSlice } from "@reduxjs/toolkit";
const slice = createSlice({
name: "contador",
initialState: { valor: 0 },
reducers: {
// parece mutacion, pero Immer la vuelve inmutable por debajo
incrementar: (estado) => { estado.valor += 1; },
},
});La victoria de Redux encierra el principio más importante de este nivel entero, y es un principio que gobierna la ingeniería mucho más allá del estado en el frontend. Redux le pide a tu código tres renuncias que parecen sacrificios: un solo store en vez de muchos, ningún dispatcher inteligente, y reducers que no pueden mutar ni tener efectos. Cada renuncia recorta lo que puedes hacer, y sin embargo la suma de las tres no te empobrece: te enriquece, porque de las restricciones brotan capacidades —viajar en el tiempo, serializar el mundo, recargar la lógica en caliente, testear sin montar nada— que un sistema libre y mutable es incapaz de ofrecer por mucho talento que le eches. Esta es la asimetría que a los principiantes les cuesta creer y que los expertos dan por sentada: la libertad no es gratis, cuesta capacidades, y a menudo el mejor trato es cambiar flexibilidad que no usas por garantías que sí necesitas. La pureza del reducer no es purismo académico; es la moneda con la que Redux compró su tooling, porque solo sobre una función determinista se puede construir una máquina que rebobine el tiempo. Ahora bien, la historia tiene una coda que impide idolatrar a Redux: en 2026 Redux ya no es la respuesta por defecto a todo. Para el estado de servidor mandan TanStack Query y RTK Query, que entendieron que la cache no es una máquina de estado; para el estado local ligero triunfan Zustand y Jotai por su falta de ceremonia; para flujos con transiciones complejas está XState. Redux perdió su monopolio, y hizo bien el ecosistema en quitárselo, porque el store único global era exceso de ceremonia para la mayoría de los casos. Pero fíjate en lo que no murió: el reducer. La función pura (estado, accion) => nuevoEstado está hoy en useReducer, en el fondo de useState, en el createSlice de RTK, en la Elm Architecture, en el update de cada statechart. La librería cedió terreno; la idea se volvió universal. Y esa es, una vez más y por última vez en este nivel, la moraleja que debes llevarte: las herramientas ganan y pierden guerras, pero las buenas ideas —el hecho con nombre, el sentido único, la transición pura— no se retiran nunca, solo cambian de nombre y siguen trabajando para ti donde ya no las ves.
- Escribe un
createStoremínimo como el de esta lección y verifica quedispatchno hace más que llamar al reducer, guardar el resultado y avisar. Comprueba que no necesitas ningúnwaitFor. - Convierte dos stores de Flux con una dependencia entre ellos en un único reducer combinado con
combineReducers. Observa cómo la dependencia se vuelve un simple orden de campos en el árbol. - Escribe un reducer y luego rómpelo a propósito metiéndole un efecto —un
Date.nowo unfetch—. Argumenta qué garantía concreta pierdes con cada impureza. - Reescribe ese mismo reducer con
createSlicede Redux Toolkit y localiza dónde Immer te deja “mutar” sin perder la pureza real. - Para tres partes del estado de una app tuya, decide si en 2026 usarías Redux Toolkit, TanStack Query, Zustand o XState, y justifica cada elección por la naturaleza del estado, no por la moda.
- Cierra el nivel: junta la frase del problema de la lección uno y la de la idea de Flux de la lección cuatro, y explica en un párrafo cómo Redux es la respuesta más afinada a aquella primera pregunta.