Redux
Contenedor de estado predecible con acciones, reducers y un store único
Redux - Contenedor de Estado Predecible
Redux es un contenedor de estado predecible para aplicaciones JavaScript. Te ayuda a escribir aplicaciones que se comportan de manera consistente, funcionan en diferentes entornos y son fáciles de probar. Redux se basa en tres principios fundamentales: fuente única de verdad, el estado es de solo lectura y los cambios se hacen con funciones puras.
Conceptos Fundamentales
- Store — Objeto único que contiene todo el estado de la aplicación
- Actions — Objetos planos que describen lo que sucedió
- Reducers — Funciones puras que especifican cómo cambia el estado
- Dispatch — Método para enviar acciones al store
- Selectors — Funciones para extraer datos del estado
El Flujo de Redux
Evento UI → dispatch(acción) → Reducer → Nuevo Estado → Actualización UI
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ UI │────▶│ Action │────▶│ Reducer │────▶│ Store │
│ (Vista) │ │ Creator │ │ │ │ (Estado)│
└─────────┘ └─────────┘ └─────────┘ └────┬────┘
▲ │
└────────────────────────────────────────────────┘
Subscribe y Re-renderizar
Actions
// Las acciones son objetos planos con una propiedad type
const agregarTodo = {
type: 'todos/agregar',
payload: {
id: 1,
texto: 'Aprender Redux',
completado: false
}
};
// Action creators - funciones que retornan acciones
function agregarTodo(texto) {
return {
type: 'todos/agregar',
payload: {
id: Date.now(),
texto,
completado: false
}
};
}
function alternarTodo(id) {
return {
type: 'todos/alternar',
payload: { id }
};
}
function eliminarTodo(id) {
return {
type: 'todos/eliminar',
payload: { id }
};
}
// Constantes de tipos de acción (previene errores tipográficos)
const AGREGAR_TODO = 'todos/agregar';
const ALTERNAR_TODO = 'todos/alternar';
const ELIMINAR_TODO = 'todos/eliminar';
Reducers
// Reducer es una función pura: (estado, acción) => nuevoEstado
const estadoInicial = {
todos: [],
filtro: 'todos'
};
function todoReducer(estado = estadoInicial, accion) {
switch (accion.type) {
case 'todos/agregar':
return {
...estado,
todos: [...estado.todos, accion.payload]
};
case 'todos/alternar':
return {
...estado,
todos: estado.todos.map(todo =>
todo.id === accion.payload.id
? { ...todo, completado: !todo.completado }
: todo
)
};
case 'todos/eliminar':
return {
...estado,
todos: estado.todos.filter(todo => todo.id !== accion.payload.id)
};
case 'filtro/establecer':
return {
...estado,
filtro: accion.payload
};
default:
return estado;
}
}
// Combinar múltiples reducers
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
todos: todoReducer,
usuario: usuarioReducer,
configuracion: configuracionReducer
});
// La forma del estado será:
// { todos: {...}, usuario: {...}, configuracion: {...} }
Store
import { createStore } from 'redux';
// Crear el store con el root reducer
const store = createStore(rootReducer);
// Obtener estado actual
console.log(store.getState());
// Despachar acciones para actualizar el estado
store.dispatch(agregarTodo('Aprender Redux'));
store.dispatch(alternarTodo(1));
// Suscribirse a cambios de estado
const cancelarSuscripcion = store.subscribe(() => {
console.log('Estado actualizado:', store.getState());
});
// Luego, dejar de escuchar
cancelarSuscripcion();
Integración con React-Redux
import { Provider, useSelector, useDispatch } from 'react-redux';
import { createStore } from 'redux';
// 1. Crear store
const store = createStore(rootReducer);
// 2. Envolver app con Provider
function App() {
return (
<Provider store={store}>
<TodoApp />
</Provider>
);
}
// 3. Usar hooks para acceder al estado y dispatch
function ListaTodos() {
// Seleccionar datos del store
const todos = useSelector(estado => estado.todos);
const filtro = useSelector(estado => estado.filtro);
// Obtener función dispatch
const dispatch = useDispatch();
const todosFiltrados = todos.filter(todo => {
if (filtro === 'activos') return !todo.completado;
if (filtro === 'completados') return todo.completado;
return true;
});
return (
<ul>
{todosFiltrados.map(todo => (
<li
key={todo.id}
onClick={() => dispatch(alternarTodo(todo.id))}
style={{ textDecoration: todo.completado ? 'line-through' : 'none' }}
>
{todo.texto}
<button onClick={() => dispatch(eliminarTodo(todo.id))}>
Eliminar
</button>
</li>
))}
</ul>
);
}
function AgregarTodo() {
const [texto, setTexto] = useState('');
const dispatch = useDispatch();
const manejarEnvio = (e) => {
e.preventDefault();
if (texto.trim()) {
dispatch(agregarTodo(texto));
setTexto('');
}
};
return (
<form onSubmit={manejarEnvio}>
<input
value={texto}
onChange={(e) => setTexto(e.target.value)}
placeholder="Agregar todo"
/>
<button type="submit">Agregar</button>
</form>
);
}
Selectors
// Selectors básicos
const seleccionarTodos = estado => estado.todos;
const seleccionarFiltro = estado => estado.filtro;
// Selectors de datos derivados
const seleccionarTodosCompletados = estado =>
estado.todos.filter(todo => todo.completado);
const seleccionarTodosActivos = estado =>
estado.todos.filter(todo => !todo.completado);
const seleccionarConteoTodos = estado => estado.todos.length;
// Selector parametrizado
const seleccionarTodoPorId = (estado, todoId) =>
estado.todos.find(todo => todo.id === todoId);
// Uso en componentes
function EstadisticasTodos() {
const total = useSelector(seleccionarConteoTodos);
const completados = useSelector(estado => seleccionarTodosCompletados(estado).length);
const activos = useSelector(estado => seleccionarTodosActivos(estado).length);
return (
<div>
Total: {total} | Activos: {activos} | Completados: {completados}
</div>
);
}
Pros y Contras de Redux
| Pros | Contras |
|---|---|
| Cambios de estado predecibles | Mucho código repetitivo (boilerplate) |
| Excelentes DevTools | Curva de aprendizaje empinada |
| Depuración con viaje en el tiempo | Puede ser excesivo para apps pequeñas |
| Gran ecosistema y comunidad | Configuración verbosa de action/reducer |
💡 Puntos Clave
- • Usa Redux para apps complejas con mucho estado compartido
- • Mantén los reducers puros - sin efectos secundarios, sin mutaciones
- • Usa selectors para encapsular el acceso al estado
- • Considera Redux Toolkit para desarrollo moderno de Redux
- • Instala la extensión Redux DevTools del navegador para depuración