Redux simplifica la gestión del estado en aplicaciones JavaScript, asegurando la consistencia del comportamiento y facilitando las pruebas y el rastreo.
El flujo básico de Redux comprende los siguientes pasos:
createStore: Crea el contenedor principal del estado (store).reducer: Una función pura que define cómo evoluciona el estado. Recibe el estado anterior y una acción, y devuevle el nuevo estado.getState: Permite consultar el estado actual del store.dispatch: Envía acciones al store para iniciar actualizaciones de estado.subscribe: Permite registrar observadores que se notificarán ante cualquier cambio en el estado.
Ejemplo de Sincronización Básica con Redux
El siguiente código ilustra una implementación simple de Redux para un contador.
// App.js
import React from "react";
import CounterView from "./views/CounterView";
function App() {
return (
<div className="App">
<CounterView />
</div>
);
}
export default App;
// store/counterStore.js
import { createStore } from "redux";
// Reducer para manejar las operaciones del contador
function counterReducer(currentState = 0, action) {
switch (action.type) {
case "INCREMENT":
return currentState + 1;
case "DECREMENT":
return currentState - 1;
default:
return currentState;
}
}
const appStore = createStore(counterReducer);
export default appStore;
// views/CounterView.js
import React, { Component } from "react";
import appStore from "../store/counterStore";
export default class CounterView extends Component {
componentDidMount() {
// Suscribirse a los cambios del store
this.unsubscribe = appStore.subscribe(() => {
// Forzar la actualización del componente al cambiar el estado
this.forceUpdate();
});
}
handleIncrement = () => {
// Despachar la acción para incrementar
appStore.dispatch({ type: "INCREMENT" });
};
handleDecrement = () => {
// Despachar la acción para decrementar
appStore.dispatch({ type: "DECREMENT" });
};
componentWillUnmount() {
// Limpiar la suscripción al desmontar el componente
if (this.unsubscribe) {
this.unsubscribe();
}
}
render() {
// Obtener el estado actual
const count = appStore.getState();
return (
<div>
<h3>Contador Redux</h3>
<p>Valor: {count}</p>
<button onClick={this.handleIncrement}>Aumentar</button>
<button onClick={this.handleDecrement}>Disminuir</button>
</div>
);
}
}
Manejo de Operaciones Asíncronas con Middleware (Redux-Thunk)
Por defecto, dispatch solo acepta objetos de acción. Para manejar lógica asíncrona, como llamadas a APIs, se utilizna middlewares. redux-thunk es una opción popular.
// store/asyncCounterStore.js
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import logger from "redux-logger"; // Middleware para logging de acciones
// Reducer actualizado para aceptar payload
function asyncCounterReducer(currentState = 0, action) {
switch (action.type) {
case "ADD_VALUE":
return currentState + (action.payload || 1);
case "SUBTRACT_VALUE":
return currentState - (action.payload || 1);
default:
return currentState;
}
}
// Aplicar middlewares al crear el store
const asyncAppStore = createStore(asyncCounterReducer, applyMiddleware(thunk, logger));
export default asyncAppStore;
// views/AsyncCounterView.js
import React, { Component } from "react";
import asyncAppStore from "../store/asyncCounterStore";
export default class AsyncCounterView extends Component {
componentDidMount() {
this.unsubscribe = asyncAppStore.subscribe(() => {
this.forceUpdate();
});
}
increment = () => {
asyncAppStore.dispatch({ type: "ADD_VALUE", payload: 5 });
};
decrement = () => {
asyncAppStore.dispatch({ type: "SUBTRACT_VALUE", payload: 5 });
};
// Acción asíncrona usando thunk
asyncIncrement = () => {
asyncAppStore.dispatch((dispatch, getState) => {
console.log("Iniciando operación asíncrona...");
// Simulación de llamada a API con setTimeout
setTimeout(() => {
console.log("Operación asíncrona completada.");
// Despachar la acción de actualización después del retraso
dispatch({ type: "ADD_VALUE", payload: 10 });
}, 1500);
});
};
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
render() {
const currentCount = asyncAppStore.getState();
return (
<div>
<h3>Contador Asíncrono con Redux</h3>
<p>Valor: {currentCount}</p>
<button onClick={this.increment}>Aumentar (+5)</button>
<button onClick={this.decrement}>Disminuir (-5)</button>
<button onClick={this.asyncIncrement}>Aumento Asíncrono (+10)</button>
</div>
);
}
}
Organización del Estado con combineReducers
Para aplicaciones más grandes, es recomendable dividir el estado en partes más pequeñas y manejarlas con combineReducers.
// store/combinedStore.js
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunk from "redux-thunk";
import logger from "redux-logger";
// Reducer para el contador
export const counterReducer = (state = 0, { type, payload = 1 }) => {
switch (type) {
case "ADD":
return state + payload;
case "SUBTRACT":
return state - payload;
default:
return state;
}
};
// Se pueden definir otros reducers aquí para diferentes partes del estado
// Combinar todos los reducers en uno solo
const rootReducer = combineReducers({
counterState: counterReducer,
// otherState: otherReducer,
});
// Crear el store con el rootReducer y middlewares
const configuredStore = createStore(
rootReducer,
applyMiddleware(thunk, logger)
);
export default configuredStore;
Para acceder al estado combinado:
// Acceso al estado específico del contador
<p>Valor del contador: {configuredStore.getState().counterState}</p>