Redux y Flux — introducción y guía práctica
Introducción — el problema inicial que enfrentaba React
Cuando React se popularizó ofrecía composición por componentes y renderizado reactivo: al cambiar props o state, la UI se actualiza de forma eficiente. En aplicaciones medianas o grandes surgieron varias fricciones prácticas:
- Estado compartido entre muchos componentes (usuario, carrito, preferencias).
- "Prop drilling": pasar datos y callbacks por capas intermedias que no los usan.
- Actualizaciones inconsistentes y condiciones de carrera si varias unidades gestionan el mismo dato.
- Difícil trazabilidad: cualquiera puede llamar setState o mutar datos y no queda claro quién cambió qué.
- Falta de convención global para flujo de datos → soluciones ad-hoc y difícil mantenimiento.
Estas limitaciones motivaron patrones que buscan previsibilidad, un único punto de verdad y facilidad para depurar.
Flux: idea central y componentes
Flux es una arquitectura con flujo unidireccional. Elementos conceptuales:
- Actions: objetos que describen qué ocurrió (ej.
{ type: 'ADD_TODO', payload: {...} }). - Dispatcher: canal que recibe acciones y las distribuye.
- Stores: contienen parte del estado y la lógica para actualizarlo.
- Views (componentes): escuchan cambios en los stores y disparan actions.
Flujo simplificado:
Beneficios:
- Evita actualizaciones caóticas (flujo predecible).
- Separa responsabilidades (presentación vs estado).
- Mejora trazabilidad y reduce prop drilling.
Limitaciones:
- Multiples stores y dispatcher introducen coordinación y boilerplate.
- Aún puede faltar una única fuente de verdad clara.
Redux: evolución práctica de Flux
Redux formaliza y simplifica Flux con tres principios:
- Single source of truth: un único árbol de estado en un store.
- El estado es de solo lectura: solo cambia mediante actions.
- Cambios realizados por reducers puros:
(state, action) => newState.
Componentes:
- Store único con
getState(),dispatch(action),subscribe(listener). - Actions: objetos planos
{ type, payload }. - Reducers: funciones puras que retornan nuevo estado.
- Middleware: capas opcionales entre dispatch y reducers para side-effects.
- Selectores: funciones para leer partes del estado (posible memoización).
Ejemplo simple:
// action
{ type: 'INCREMENT' }
// reducer
function counter(state = { count: 0 }, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
default:
return state;
}
}
¿Por qué mejora sobre Flux?
- Un único store evita dispersión del estado.
- Reducers puros garantizan previsibilidad y testabilidad.
- Permite time-travel/debugging por acciones serializables e inmutabilidad.
- Ecosistema maduro (DevTools, middleware, RTK).
Single Source of Truth (SSOT) — explicación y beneficios
SSOT: todo el estado de la app reside en un único objeto/árbol.
Beneficios concretos:
- Consistencia: una sola referencia canónica.
- Sincronización: todos consumen la misma vista tras actualizaciones.
- Depuración/auditoría: registrar acciones y estado facilita reproducir bugs.
- Persistencia/SSR: serializar y restaurar estado es directo.
- Undo/Redo y reproducibilidad.
Riesgos:
- Store muy grande si no se normaliza. Mantener solo lo esencial.
- Posible over-engineering en apps pequeñas.
Cómo Redux/Flux solucionan los problemas iniciales
- Prop drilling: componentes se suscriben al store (connect/useSelector) en lugar de pasar props.
- Inconsistencias: acciones y reducers puros dan cambios determinísticos.
- Debugging: historial de acciones y estado anterior/posterior facilita tracing.
- Reglas claras: solo las actions pueden modificar el estado.
- Testabilidad: reducers y selectores son fácilmente testeables.
Patrones prácticos y buenas prácticas
- Normalizar el estado (byId + allIds) para evitar duplicación.
- No guardar datos derivados; usar selectores o memoización.
- Mantener UI local en el componente (modales, inputs efímeros).
- Usar selectors (reselect) para evitar renders innecesarios.
- Favorecer inmutabilidad (spread, Object.assign o immer).
- Separar lógica asíncrona en middleware (thunks, sagas).
- Dividir reducers con combineReducers en slices.
- Usar TypeScript para seguridad de tipos en proyectos grandes.
Rendimiento y optimizaciones
- Conectar componentes solo a las partes del estado que necesitan.
- Usar PureComponent / React.memo y comparar por referencia.
- Normalizar datos y acceder por id.
- Agrupar múltiples cambios en una sola action cuando corresponda.
Alternativas y evolución del ecosistema
- Context API + hooks (useReducer/useContext): buena para casos específicos/medianos.
- Redux Toolkit (RTK): reduce boilerplate, incluye createSlice y usa immer.
- Librerías modernas: MobX, Zustand, Jotai — cada una con trade-offs.
- Server state managers: React Query, SWR — orientados a cache de servidor y suelen convivir con Redux.
¿Cuándo usar Redux?
Úsalo si:
- Mucho estado compartido entre componentes.
- Necesitas debugging avanzado, time-travel o serialización.
- Requieres predictibilidad y testabilidad del estado.
Evítalo si:
- La app es pequeña o el estado es mayoritariamente local.
- Prefieres soluciones más ligeras para casos puntuales.