El store y combineReducers
El store es la encarnación física de la fuente única de verdad: un solo objeto que createStore construye a partir de un reducer raíz. Pero un reducer monolítico no escala, y aquí entra combineReducers, que compone un reducer por dominio en un árbol donde cada clave es una porción gobernada por exactamente un reducer. Esta lección explica cómo nace el store, cómo la composición reparte el árbol en regiones con un solo escritor cada una, y por qué la forma del árbol de estado es una decisión de arquitectura tan seria como el mapa de propiedad del nivel 1. Cierra con configureStore y createSlice, el Redux moderno de 2026 que llama a combineReducers por ti.
Llegamos a la pieza que sostiene a las otras tres y cierra el nivel: el store. Si las acciones son los hechos y los reducers la lógica que los interpreta, el store es el lugar físico donde todo eso ocurre y donde el estado, uno y único, reside. Pero aquí surge la tensión que define la arquitectura de una app Redux real: un solo store no significa un solo reducer gigante. Un monolito que gestione a la vez el usuario, el carrito, la interfaz y la caché sería ingobernable. La respuesta de Redux es la composición: muchos reducers pequeños, uno por dominio, ensamblados por combineReducers en un único árbol donde cada uno reina sobre su parcela y nadie pisa la del vecino. Diseñar la forma de ese árbol es, sin exagerar, la decisión de arquitectura más importante que tomarás con Redux.
- Ver
createStorecomo el nacimiento de la fuente única de verdad. - Componer un reducer por dominio con
combineReducersen un solo árbol. - Entender que cada porción tiene un único escritor: su reducer.
- Diseñar la forma del árbol de estado y migrarla a
configureStoreen 2026.
createStore: nace la fuente única
createStore toma un reducer y devuelve el store: el objeto con getState, dispatch y subscribe que viste en la lección anterior. Acepta también un estado precargado —útil para hidratar desde el servidor— y un enhancer para inyectar middleware. Pero su esencia es esta: a partir de una función pura que sabe transformar el estado, fabrica el contenedor vivo que guarda ese estado y coordina sus cambios. Un store por aplicación; esa unicidad es el primer principio hecho objeto.
import { createStore } from 'redux' // legado; en 2026 usa configureStore
const store = createStore(reducerRaiz, estadoPrecargado)
El problema aparece al crecer. Si reducerRaiz fuera una sola función con un switch de cincuenta casos que gestiona usuario, tareas y UI a la vez, tendrías la lógica de tres dominios enredada en un solo lugar, con un estado que es un objeto plano gigante y sin fronteras claras de quién puede tocar qué. Redux nunca pidió eso. Pidió un solo store, no un solo reducer.
combineReducers: un reducer por dominio
combineReducers es la herramienta que resuelve la tensión. Recibe un objeto donde cada clave nombra una porción del estado y cada valor es el reducer que gobierna esa porción, y devuelve un único reducer raíz que delega. Cuando llega una acción, ese reducer raíz la pasa a cada reducer hijo junto con su propia rebanada del estado, recoge lo que cada uno devuelve y compone el árbol resultante. La forma del estado global queda determinada, exactamente, por las claves que le des a combineReducers.
import { combineReducers } from 'redux'
const reducerRaiz = combineReducers({
usuario: usuarioReducer, // gobierna estado.usuario y nada mas
todos: todosReducer, // gobierna estado.todos y nada mas
ui: uiReducer, // gobierna estado.ui y nada mas
})
// El arbol resultante:
// { usuario: {...}, todos: {...}, ui: {...} }
La regla de aislamiento es estricta y es la clave de que esto escale: todosReducer solo recibe estado.todos, solo puede devolver estado.todos, y no ve ni toca estado.usuario. Cada reducer vive en su burbuja, ignorante de las demás. Esto significa que cada porción del árbol tiene un único escritor —su reducer—, lo cual es el principio del escritor único del nivel 1 elevado a ley estructural: no es que convengas en no tocar la parcela ajena, es que combineReducers hace imposible tocarla.
flowchart TD A[accion] --> R[reducer raiz] R --> U[usuario reducer] R --> T[todos reducer] R --> I[ui reducer] U --> AU[estado.usuario] T --> AT[estado.todos] I --> AI[estado.ui] style R fill:#cba6f7,color:#11111b style A fill:#f9e2af,color:#11111b style U fill:#89b4fa,color:#11111b style T fill:#89b4fa,color:#11111b style I fill:#89b4fa,color:#11111b
Un malentendido común es creer que una acción va a un solo reducer. No: combineReducers pasa cada acción a todos los reducers hijos. La mayoría la ignorará devolviendo su porción intacta —de ahí la regla del default que devuelve el estado—, pero varios pueden reaccionar a la misma acción a la vez. Esto es lo que hace útil el modelar eventos en vez de comandos: un sesion/usuarioSalio puede limpiar estado.usuario, vaciar estado.carrito y resetear estado.ui en un solo despacho, porque los tres reducers lo escuchan.
La forma del árbol de estado
Decidir la forma del árbol es el equivalente en Redux a trazar el mapa de propiedad del nivel 1, y merece la misma seriedad. Tres reglas guían un buen diseño. Primera: organiza por dominio, no por vista; el árbol refleja los datos del negocio —usuario, pedidos, productos—, no las pantallas que los muestran, porque las pantallas cambian y los dominios perduran. Segunda: mantenlo plano y normalizado; en lugar de anidar arrays de objetos, guarda entidades por su id, tal como enseñaba la normalización del nivel 1, para que actualizar una sea cambiar una entrada y no recorrer una lista. Tercera: separa el estado de servidor del de cliente; los datos que vienen de una API son caché, y en 2026 casi nunca deberían vivir a mano en el árbol.
Por dominio, no por vista
Las claves del árbol son los dominios del negocio, no las pantallas. La UI cambia cada temporada; el modelo de datos, no. Un árbol atado a vistas envejece mal.
Plano y normalizado
Entidades por id en un mapa, no arrays anidados de objetos. Actualizar es tocar una clave. createEntityAdapter de RTK da esta forma y sus selectores gratis.
Servidor aparte
Lo que viene de una API es caché, no verdad local. En 2026 lo gestiona RTK Query o TanStack Query; no lo copies a mano dentro del árbol.
Estas decisiones tienen consecuencias que se pagan durante años. Un árbol organizado por vistas obliga a duplicar el mismo dato en dos ramas cuando dos pantallas lo muestran —el bug de las dos verdades del nivel 1, ahora dentro del store—. Un árbol sin normalizar convierte cada actualización de una entidad que aparece en varias listas en una cacería. Un árbol que guarda a mano datos de servidor termina sirviendo información obsoleta porque nadie lo revalida. La forma del árbol no es un detalle de implementación: es la arquitectura, y errarla no se arregla con mejores reducers.
2026: configureStore y el Redux moderno
En la práctica actual, ni createStore ni combineReducers se escriben ya de forma explícita. configureStore de Redux Toolkit los absorbe: le pasas un objeto reducer con tus reducers por dominio y él llama a combineReducers internamente, añade el middleware thunk, conecta las DevTools y activa las comprobaciones de inmutabilidad y serializabilidad en desarrollo, todo con valores por defecto sensatos.
import { configureStore } from '@reduxjs/toolkit'
import usuarioReducer from './usuarioSlice'
import todosReducer from './todosSlice'
export const store = configureStore({
reducer: { // configureStore llama a combineReducers por ti
usuario: usuarioReducer,
todos: todosReducer,
},
})
export type RootState = ReturnType<typeof store.getState>
| Aspecto | createStore (legado) |
configureStore (2026) |
|---|---|---|
| Composición | combineReducers a mano |
Objeto reducer, combinado por ti |
| Middleware | Enhancer manual | thunk incluido por defecto |
| DevTools | Configuración a mano | Conectadas automáticamente |
| Seguridad en dev | Ninguna | Chequeos de inmutabilidad y serializabilidad |
Aquí se cierra el círculo que abrió el nivel 1. Aquel principio abstracto —cada dato tiene un dueño único, y la consistencia se logra por ausencia de copias, no por vigilancia— se vuelve, en Redux, un objeto que puedes señalar con el dedo: el store. Y combineReducers es la respuesta a la pregunta que el nivel 1 dejaba abierta: cómo se escala una única fuente de verdad a una aplicación con decenas de dominios sin fragmentarla en muchas fuentes que vuelvan a desincronizarse. La respuesta es que no fragmentas el store, fragmentas su gobierno: sigue habiendo un solo árbol, una sola fuente, pero repartida en regiones donde cada reducer es el único escritor de la suya. La forma de ese árbol es, literalmente, la arquitectura de datos de tu aplicación hecha explícita en un objeto que puedes leer de un vistazo, y ese es uno de los grandes regalos de Redux frente a la reactividad implícita de los signals: no tienes que reconstruir mentalmente dónde vive cada dato, porque está todo ahí, en un árbol con nombres, con un solo dueño por rama y una sola vía de cambio. Cuando diseñes ese árbol, no estás configurando una librería: estás dibujando el mapa de propiedad del nivel 1 en código ejecutable. Por eso el consejo final del nivel es el mismo con el que empezó todo el track: antes de escribir un reducer, dibuja la forma del estado —qué dominios, qué entidades, qué es cliente y qué es caché de servidor— y nombra el único dueño de cada rama. Redux Toolkit escribirá el combineStore por ti; la forma del árbol, esa, sigue siendo tuya, y es lo único que ninguna herramienta puede acertar en tu lugar.
- Toma una app con estado disperso y dibuja su árbol Redux ideal: claves por dominio, no por pantalla. Nombra el único reducer dueño de cada rama.
- Escribe dos o tres reducers de dominio y ensámblalos con
combineReducers. Comprueba que cada uno solo ve y devuelve su porción. - Despacha una acción de tipo evento —
sesion/usuarioSalio— y verifica que varias ramas reaccionan a la vez en un solodispatch. - Normaliza una rama que hoy sea un array de objetos: pásala a un mapa por id y observa cómo se simplifican las actualizaciones.
- Migra todo a
configureStorede Redux Toolkit, define el tipoRootState, y localiza qué datos de tu árbol eran en realidad caché de servidor que debería gestionar RTK Query.