🔴 Redux Cheatsheet Completo 🔴
Redux es una librería de JavaScript para gestionar el estado de la aplicación. Proporciona un “store” centralizado para todo el estado de la aplicación, facilitando la predicción del comportamiento del estado. Es comúnmente utilizado con React, pero es agnóstico a la capa de UI.
1. 🌟 Principios Fundamentales
Redux se adhiere a tres principios básicos:
- Única fuente de verdad (Single source of truth): El estado de toda tu aplicación se almacena en un solo objeto dentro de un
Storeúnico. - El estado es de solo lectura (State is read-only): La única forma de cambiar el estado es emitiendo una
Action, un objeto que describe lo que sucedió. No puedes modificar el estado directamente. - Los cambios se realizan con funciones puras (Changes are made with pure functions): Para especificar cómo el árbol de estado es transformado por las
Actions, escribesReducerspuros.
2. 🧩 Componentes Clave
- Store: El objeto que contiene el estado de la aplicación.
- Métodos clave:
getState(),dispatch(action),subscribe(listener).
- Métodos clave:
- Action (Acción): Un objeto JavaScript plano que describe lo que sucedió. Es la única forma de enviar datos al
Store. Deben tener una propiedadtype.- Ej:
{ type: 'ADD_TODO', text: 'Learn Redux' }
- Ej:
- Action Creator (Creador de Acción): Una función que crea y devuelve un objeto
Action.- Ej:
function addTodo(text) { return { type: 'ADD_TODO', text }; }
- Ej:
- Reducer (Reductor): Una función pura que toma el
estado actualde la aplicación y unaacción, y devuelve elnuevo estado.(state, action) => newState- ¡Nunca muta el estado directamente! Siempre devuelve un nuevo objeto de estado.
- Middleware: Proporciona un punto de extensión entre el
dispatchde unaActiony el momento en que llega alReducer. Útil para lógica asíncrona, logging, etc. (ej. Redux Thunk, Redux Saga).
3. 🛠️ Configuración Inicial (Redux Toolkit - ¡Recomendado!)
Redux Toolkit (RTK) es el enfoque recomendado y estándar para escribir lógica Redux. Simplifica el boilerplate y añade buenas prácticas.
- Instalación (con React Redux):
npm install @reduxjs/toolkit react-redux # o yarn add @reduxjs/toolkit react-redux - Crear un Store (
src/app/store.jsostore.ts):import { configureStore } from '@reduxjs/toolkit'; import counterReducer from '../features/counter/counterSlice'; // Importa tu reducer de slice export const store = configureStore({ reducer: { counter: counterReducer, // Cada slice de estado se asigna a una clave // ... otros reducers de slices }, // Middleware por defecto de RTK para manejo inmutable y serializable // devTools: process.env.NODE_ENV !== 'production', // Habilitar DevTools solo en desarrollo }); - Proveer el Store a la Aplicación React (
src/index.jsomain.jsx):import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App.js'; import { store } from './app/store.js'; // Importa tu store import { Provider } from 'react-redux'; // Importa Provider de react-redux ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <Provider store={store}> {/* Envuelve tu App con Provider y pásale el store */} <App /> </Provider> </React.StrictMode>, );
4. 📝 Redux Toolkit (RTK) - Slice de Estado
RTK simplifica Actions, Action Creators y Reducers en un solo concepto: el “Slice”.
4.1. Definir un Slice (src/features/counter/counterSlice.js)
import { createSlice } from '@reduxjs/toolkit';
// Define el estado inicial de este slice
const initialState = {
value: 0,
};
// Crea un slice de Redux
export const counterSlice = createSlice({
name: 'counter', // Nombre del slice (se usa como prefijo para los types de acción)
initialState, // Estado inicial
// Reducers: mapeo de nombres de acción a funciones reductoras
// 'createSlice' usa Immer para permitir "mutar" el estado en los reducers,
// pero internamente crea nuevas copias inmutables.
reducers: {
increment: (state) => {
state.value += 1; // Directamente "mutamos" el estado (gracias a Immer)
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => { // Reducer que acepta un payload
state.value += action.payload;
},
reset: (state) => {
state.value = 0; // O initialState
},
},
});
// Exporta los action creators generados automáticamente
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;
// Exporta el reducer para ser combinado en el store
export default counterSlice.reducer;
// Selectores (opcional, pero buena práctica)
// Se puede definir aquí o en un archivo aparte (ej. selectors.js)
export const selectCounterValue = (state) => state.counter.value;
5. ⚛️ Interacción con Componentes React (React Redux Hooks)
react-redux proporciona hooks para conectar componentes React al Store de Redux.
5.1. useSelector() - Obtener Estado del Store
- Permite extraer datos del estado del Store de Redux.
import React from 'react';
import { useSelector } from 'react-redux'; // Importa useSelector
import { selectCounterValue } from '../../features/counter/counterSlice'; // Importa tu selector
function CounterDisplay() {
// Usa useSelector para obtener el valor del contador del estado
const count = useSelector(selectCounterValue); // Pásale un selector
// También puedes escribir el selector en línea, pero es menos reutilizable
// const count = useSelector((state) => state.counter.value);
return (
<div>
<h3>Valor del Contador: {count}</h3>
</div>
);
}
export default CounterDisplay;
5.2. useDispatch() - Enviar Acciones al Store
- Devuelve la función
dispatchdel Store, que se utiliza para enviarActions.
import React from 'react';
import { useDispatch } from 'react-redux'; // Importa useDispatch
import { increment, decrement, incrementByAmount, reset } from '../../features/counter/counterSlice'; // Importa tus action creators
function CounterControls() {
const dispatch = useDispatch(); // Obtiene la función dispatch
return (
<div>
<button onClick={() => dispatch(increment())}>Incrementar</button>
<button onClick={() => dispatch(decrement())}>Decrementar</button>
<button onClick={() => dispatch(incrementByAmount(5))}>Incrementar en 5</button>
<button onClick={() => dispatch(reset())}>Resetear</button>
</div>
);
}
export default CounterControls;```
---
## 6. 🌐 Lógica Asíncrona (Redux Thunk - Integrado en RTK)
Para manejar efectos secundarios como llamadas a APIs. Redux Toolkit incluye Redux Thunk por defecto.
### 6.1. Definir un Thunk Asíncrono en un Slice
`createAsyncThunk` genera `Actions` de ciclo de vida (`pending`, `fulfilled`, `rejected`) y maneja la Promesa.
```javascript
// src/features/users/usersSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// 1. Define el thunk asíncrono
export const fetchUsers = createAsyncThunk(
'users/fetchUsers', // Prefijo para los tipos de acción
async (userId, thunkAPI) => { // Payload (userId) y thunkAPI (acceso a getState, dispatch, rejectWithValue)
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users${userId ? '/' + userId : ''}`);
if (!response.ok) {
// Usa rejectWithValue para pasar un error personalizado al estado 'rejected'
return thunkAPI.rejectWithValue('Error al obtener usuarios');
}
const data = await response.json();
return data; // El valor de retorno se convierte en action.payload para 'fulfilled'
} catch (error) {
return thunkAPI.rejectWithValue(error.message);
}
}
);
// Define el slice
const usersSlice = createSlice({
name: 'users',
initialState: {
list: [],
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null,
},
reducers: {
// Reducers síncronos aquí si los hay
},
// Extra reducers: para manejar acciones que no están definidas en este slice (ej. acciones de thunks)
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.status = 'succeeded';
state.list = Array.isArray(action.payload) ? action.payload : [action.payload]; // Asegura que sea un array
})
.addCase(fetchUsers.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload || action.error.message; // Usa el payload de rejectWithValue o el mensaje de error estándar
});
},
});
export default usersSlice.reducer;
// Selector de ejemplo
export const selectAllUsers = (state) => state.users.list;
export const selectUsersStatus = (state) => state.users.status;
export const selectUsersError = (state) => state.users.error;
6.2. Usar un Thunk Asíncrono en un Componente
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUsers, selectAllUsers, selectUsersStatus, selectUsersError } from '../../features/users/usersSlice';
function UserList() {
const dispatch = useDispatch();
const users = useSelector(selectAllUsers);
const status = useSelector(selectUsersStatus);
const error = useSelector(selectUsersError);
useEffect(() => {
// Solo si el estado es 'idle', despacha la acción asíncrona
if (status === 'idle') {
dispatch(fetchUsers()); // Dispara el thunk
}
}, [status, dispatch]); // Dependencias para evitar bucle infinito
if (status === 'loading') {
return <div>Cargando usuarios...</div>;
}
if (status === 'failed') {
return <div>Error al cargar usuarios: {error}</div>;
}
return (
<div>
<h2>Usuarios</h2>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
export default UserList;
7. 🧰 Redux DevTools Extension
Una extensión de navegador esencial para depurar aplicaciones Redux.
- Permite inspeccionar el estado del Store en cualquier momento, ver el historial de
Actionsdespachadas, y viajar en el tiempo entre estados. - Redux Toolkit configura automáticamente la integración con DevTools.
8. 💡 Buenas Prácticas y Consejos
- Siempre usa Redux Toolkit (RTK): Elimina el boilerplate y aplica las mejores prácticas por defecto (Inmutabilidad con Immer, Redux Thunk integrado, simplificación de Slices).
- Diseño de Slices Basado en Características: Organiza tu Store dividiéndolo en “slices” lógicos basados en las características de tu aplicación (ej.
counterSlice,usersSlice,postsSlice). - Normalización del Estado: Para datos relacionales (ej. usuarios, posts), almacénalos en un formato normalizado (ej.
{ ids: [], entities: { id1: user1, id2: user2 } }) para facilitar las actualizaciones y consultas. RTK proporcionacreateEntityAdapterpara ayudar con esto. - Selectores Reutilizables y Memorizados: Define selectores puros para extraer datos del Store. Usa
createSelectordereselect(integrado en RTK) para crear selectores memorizados que solo recalculan si sus entradas cambian. - Minimiza el Disparo de Acciones: Despacha acciones solo cuando un cambio de estado es significativo.
- Lógica Asíncrona con
createAsyncThunk: Es el enfoque estándar de RTK para manejar llamadas a APIs y otros efectos secundarios. - Evita la Mutación del Estado: Aunque Immer en RTK lo permite en reducers, ten en cuenta que el estado de Redux debe ser conceptualmente inmutable.
- No guardes datos derivados en el Store: Si un dato puede ser calculado a partir del estado existente, calcúlalo con un selector en lugar de almacenarlo directamente.
- Usa TypeScript: Redux Toolkit tiene un excelente soporte para TypeScript, lo que mejora la seguridad de tipos y la experiencia del desarrollador.
- Evita el “Prop Drilling”: Redux ayuda a evitarlo, pero asegúrate de no caer en él al pasar selectores o despachadores a componentes profundos.
Este cheatsheet te proporciona una referencia completa de Redux, cubriendo sus principios fundamentales, el uso de Redux Toolkit (el enfoque moderno), la interacción con React, la lógica asíncrona y las mejores prácticas para gestionar el estado de forma predecible y eficiente en tus aplicaciones.