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-thunkjunto con nuestrologgerMiddlewareymonitorReducerEnhancer, además de dos funciones adicionales proporcionadas por Redux:applyMiddlewareycompose. -
Luego usamos
applyMiddlewarepara crear un enhancer de store que aplicará nuestrologgerMiddlewarey el middlewarethunka la función dispatch de la store. -
Después, usamos
composepara combinar nuestro nuevomiddlewareEnhancery nuestromonitorReducerEnhanceren 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
composedEnhancersacreateStorecomo 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
middlewarescomoenhancersse 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
preloadedStatese pasa acreateStorepor 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
applyMiddlewarecon una lista predeterminada de middleware, incluyendoredux-thunk, y algunos específicos para desarrollo que detectan errores comunes como mutar el estado -
Llamará a
composeWithDevToolspara 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.