Ir al contenido principal
Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Configuración de tu Store

En el tutorial "Fundamentos de Redux", presentamos los conceptos fundamentales de Redux construyendo una app de lista de tareas. Como parte de eso, explicamos cómo crear y configurar una store de Redux.

Ahora exploraremos cómo personalizar la store para añadir funcionalidad extra. Comenzaremos con el código fuente de "Fundamentos de Redux" parte 5: UI y React. Puedes ver el código en esta etapa del tutorial en el repositorio de ejemplo en Github o en tu navegador vía CodeSandbox.

Creando la store

Primero, veamos el archivo index.js original donde creamos nuestra store:

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
import App from './components/App'

const store = createStore(rootReducer)

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

En este código, pasamos nuestros reducers a la función createStore de Redux, que devuelve un objeto store. Luego pasamos este objeto al componente Provider de react-redux, que se renderiza en la parte superior de nuestro árbol de componentes.

Esto garantiza que cada vez que nos conectemos a Redux en nuestra app mediante connect de react-redux, la store estará disponible para nuestros componentes.

Extendiendo la funcionalidad de Redux

La mayoría de apps extienden la funcionalidad de su store de Redux añadiendo middleware o enhancers (nota: el middleware es común, los enhancers menos comunes). El middleware añade funcionalidad extra a la función dispatch de Redux; los enhancers añaden funcionalidad extra a la store de Redux.

Añadiremos dos middlewares y un enhancer:

  • El middleware redux-thunk, que permite uso asíncrono simple de dispatch.

  • Un middleware que registra las acciones despachadas y el nuevo estado resultante.

  • Un enhancer que registra el tiempo que tardan los reducers en procesar cada acción.

Instalar redux-thunk

npm install redux-thunk

middleware/logger.js

const logger = store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd()
return result
}

export default logger

enhancers/monitorReducer.js

const round = number => Math.round(number * 100) / 100

const monitorReducerEnhancer =
createStore => (reducer, initialState, enhancer) => {
const monitoredReducer = (state, action) => {
const start = performance.now()
const newState = reducer(state, action)
const end = performance.now()
const diff = round(end - start)

console.log('reducer process time:', diff)

return newState
}

return createStore(monitoredReducer, initialState, enhancer)
}

export default monitorReducerEnhancer

Añadamos estos a nuestro index.js existente.

  • Primero, necesitamos importar redux-thunk junto con nuestro loggerMiddleware y monitorReducerEnhancer, además de dos funciones adicionales proporcionadas por Redux: applyMiddleware y compose.

  • Luego usamos applyMiddleware para crear un enhancer de store que aplicará nuestro loggerMiddleware y el middleware thunk a la función dispatch de la store.

  • Después, usamos compose para combinar nuestro nuevo middlewareEnhancer y nuestro monitorReducerEnhancer en una sola función.

    Esto es necesario porque solo puedes pasar un enhancer a createStore. Para usar múltiples enhancers, primero debes combinarlos en un único enhancer más grande, como se muestra en este ejemplo.

  • Finalmente, pasamos esta nueva función composedEnhancers a createStore como tercer argumento. Nota: el segundo argumento, que ignoraremos, permite precargar estado en la store.

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { applyMiddleware, createStore, compose } from 'redux'
import { thunk } from 'redux-thunk'
import rootReducer from './reducers'
import loggerMiddleware from './middleware/logger'
import monitorReducerEnhancer from './enhancers/monitorReducer'
import App from './components/App'

const middlewareEnhancer = applyMiddleware(loggerMiddleware, thunk)
const composedEnhancers = compose(middlewareEnhancer, monitorReducerEnhancer)

const store = createStore(rootReducer, undefined, composedEnhancers)

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

Problemas con este enfoque

Aunque este código funciona, para una app típica no es ideal.

La mayoría de apps usan más de un middleware, y cada middleware a menudo requiere configuración inicial. El ruido adicional añadido al index.js puede dificultar rápidamente su mantenimiento, porque la lógica no está organizada de forma clara.

La solución: configureStore

La solución a este problema es crear una nueva función configureStore que encapsule la lógica de creación de nuestro store, la cual puede ubicarse en su propio archivo para facilitar su extensibilidad.

El objetivo final es que nuestro index.js tenga este aspecto:

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import configureStore from './configureStore'

const store = configureStore()

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

Toda la lógica relacionada con la configuración del store —incluyendo la importación de reductores, middleware y mejoras— se maneja en un archivo dedicado.

Para lograrlo, la función configureStore luce así:

import { applyMiddleware, compose, createStore } from 'redux'
import { thunk } from 'redux-thunk'

import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'

export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunk]
const middlewareEnhancer = applyMiddleware(...middlewares)

const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = compose(...enhancers)

const store = createStore(rootReducer, preloadedState, composedEnhancers)

return store
}

Esta función sigue los mismos pasos descritos anteriormente, con parte de la lógica separada para preparar futuras extensiones, facilitando añadir más funcionalidades:

  • Tanto middlewares como enhancers se definen como arrays, separados de las funciones que los consumen.

    Esto nos permite añadir fácilmente más middleware o mejoras según diferentes condiciones.

    Por ejemplo, es común añadir cierto middleware solo en modo desarrollo, lo cual se logra fácilmente agregando al array dentro de un condicional:

    if (process.env.NODE_ENV === 'development') {
    middlewares.push(secretMiddleware)
    }
  • La variable preloadedState se pasa a createStore por si queremos utilizarla posteriormente.

Esto también hace que nuestra función createStore sea más fácil de entender —cada paso está claramente separado, haciendo más evidente lo que ocurre exactamente.

Integración de la extensión DevTools

Otra característica común que puedes querer añadir a tu aplicación es la integración con redux-devtools-extension.

Esta extensión es un conjunto de herramientas que te da control absoluto sobre tu store de Redux: permite inspeccionar y reproducir acciones, explorar tu estado en diferentes momentos, despachar acciones directamente al store y mucho más. Haz clic aquí para conocer más sobre las funciones disponibles.

Existen varias formas de integrar la extensión, pero usaremos la opción más conveniente.

Primero, instalamos el paquete vía npm:

npm install --save-dev redux-devtools-extension

Luego, reemplazamos la función compose importada de redux por una nueva función composeWithDevTools importada de redux-devtools-extension.

El código final luce así:

import { applyMiddleware, createStore } from 'redux'
import { thunk } from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'

import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'

export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunk]
const middlewareEnhancer = applyMiddleware(...middlewares)

const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = composeWithDevTools(...enhancers)

const store = createStore(rootReducer, preloadedState, composedEnhancers)

return store
}

¡Y eso es todo!

Si ahora visitamos nuestra aplicación en un navegador con la extensión DevTools instalada, podremos explorar y depurar usando esta potente nueva herramienta.

Recarga en caliente (Hot Reloading)

Otra herramienta poderosa que puede hacer el desarrollo más intuitivo es la recarga en caliente, que permite reemplazar partes del código sin reiniciar toda la aplicación.

Por ejemplo, imagina que ejecutas tu app, interactúas con ella un tiempo y luego decides hacer cambios en uno de tus reductores. Normalmente, al realizar esos cambios tu aplicación se reiniciaría, perdiendo el estado actual de Redux.

Con la recarga en caliente habilitada, solo se recargaría el reductor modificado, permitiéndote cambiar el código sin resetear el estado cada vez. Esto agiliza enormemente el desarrollo.

Añadiremos recarga en caliente tanto a nuestros reductores de Redux como a nuestros componentes de React.

Primero, añadámoslo a nuestra función configureStore:

import { applyMiddleware, compose, createStore } from 'redux'
import { thunk } from 'redux-thunk'

import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'

export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunk]
const middlewareEnhancer = applyMiddleware(...middlewares)

const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
const composedEnhancers = compose(...enhancers)

const store = createStore(rootReducer, preloadedState, composedEnhancers)

if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./reducers', () => store.replaceReducer(rootReducer))
}

return store
}

El nuevo código está envuelto en un condicional if, por lo que solo se ejecuta cuando la app no está en modo producción y cuando está disponible la función module.hot.

Empaquetadores como Webpack y Parcel soportan el método module.hot.accept para especificar qué módulo debe recargarse en caliente y qué debe ocurrir cuando cambia. En este caso, observamos el módulo ./reducers y pasamos el rootReducer actualizado al método store.replaceReducer cuando cambia.

También usaremos este patrón en nuestro index.js para recargar en caliente los cambios en nuestros componentes de React:

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import configureStore from './configureStore'

const store = configureStore()

const renderApp = () =>
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./components/App', renderApp)
}

renderApp()

El único cambio adicional aquí es que hemos encapsulado el renderizado de nuestra aplicación en una nueva función renderApp, que ahora llamamos para volver a renderizar la aplicación.

Simplificando la configuración con Redux Toolkit

La biblioteca central de Redux es deliberadamente neutral. Te permite decidir cómo manejar todo: desde la configuración del store hasta qué contiene tu estado y cómo construir tus reducers.

Esto es positivo en algunos casos porque ofrece flexibilidad, pero esa flexibilidad no siempre es necesaria. A veces simplemente queremos la forma más sencilla de comenzar, con un comportamiento predeterminado útil desde el primer momento.

El paquete Redux Toolkit está diseñado para simplificar varios casos de uso comunes de Redux, incluida la configuración del store. Veamos cómo puede mejorar este proceso.

Redux Toolkit incluye una función preconstruida configureStore como la mostrada en ejemplos anteriores.

La forma más rápida de usarla es pasar directamente la función root reducer:

import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './reducers'

const store = configureStore({
reducer: rootReducer
})

export default store

Observa que acepta un objeto con parámetros nombrados, lo que clarifica lo que estás pasando.

Por defecto, configureStore de Redux Toolkit hará:

  • Llamará a applyMiddleware con una lista predeterminada de middleware, incluyendo redux-thunk, y algunos específicos para desarrollo que detectan errores comunes como mutar el estado

  • Llamará a composeWithDevTools para configurar la extensión Redux DevTools

Así se vería el ejemplo de recarga en caliente usando Redux Toolkit:

import { configureStore } from '@reduxjs/toolkit'

import monitorReducersEnhancer from './enhancers/monitorReducers'
import loggerMiddleware from './middleware/logger'
import rootReducer from './reducers'

export default function configureAppStore(preloadedState) {
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware().prepend(loggerMiddleware),
preloadedState,
enhancers: [monitorReducersEnhancer]
})

if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('./reducers', () => store.replaceReducer(rootReducer))
}

return store
}

Esto definitivamente simplifica parte del proceso de configuración.

Pasos siguientes

Ahora que sabes cómo encapsular la configuración de tu store para facilitar su mantenimiento, puedes explorar la API de configureStore en Redux Toolkit o examinar algunas de las extensiones disponibles en el ecosistema Redux.