En este tema, aprenderemos sobre dos conceptos fundamentales en Redux: las acciones y los reductores. Estos son los bloques de construcción esenciales para gestionar el estado de una aplicación de React utilizando Redux.
¿Qué son las Acciones?
Las acciones son objetos JavaScript simples que envían datos desde tu aplicación a tu store de Redux. Son la única fuente de información para el store. Puedes pensar en las acciones como "mensajes" que describen algo que ha ocurrido en la aplicación.
Estructura de una Acción
Una acción es un objeto que debe tener una propiedad type
. El type
es una cadena que indica el tipo de acción que se está realizando. Además, una acción puede tener otras propiedades que contienen datos relevantes.
Creadores de Acciones
Los creadores de acciones son funciones que crean y retornan una acción. Son útiles para evitar la repetición de código y para mantener el código más limpio y manejable.
¿Qué son los Reductores?
Los reductores son funciones puras que toman el estado anterior y una acción, y retornan un nuevo estado. La función reductora especifica cómo cambia el estado de la aplicación en respuesta a una acción enviada al store.
Estructura de un Reductor
Un reductor es una función que acepta dos parámetros: el estado actual y una acción. Dependiendo del tipo de acción, el reductor retorna un nuevo estado.
const initialState = { todos: [] }; function todoReducer(state = initialState, action) { switch (action.type) { case 'ADD_TODO': return { ...state, todos: [...state.todos, action.payload] }; default: return state; } }
Combinando Reductores
En una aplicación grande, es común tener múltiples reductores que manejan diferentes partes del estado. Redux proporciona una función llamada combineReducers
para combinar múltiples reductores en uno solo.
import { combineReducers } from 'redux'; const rootReducer = combineReducers({ todos: todoReducer, // otros reductores pueden ir aquí }); export default rootReducer;
Ejemplo Práctico
Vamos a crear un ejemplo práctico donde implementamos acciones y reductores para una lista de tareas (todos).
Paso 1: Definir Acciones y Creadores de Acciones
// actions.js export const ADD_TODO = 'ADD_TODO'; export const REMOVE_TODO = 'REMOVE_TODO'; export function addTodoAction(id, text) { return { type: ADD_TODO, payload: { id, text } }; } export function removeTodoAction(id) { return { type: REMOVE_TODO, payload: { id } }; }
Paso 2: Crear el Reductor
// reducers.js import { ADD_TODO, REMOVE_TODO } from './actions'; const initialState = { todos: [] }; function todoReducer(state = initialState, action) { switch (action.type) { case ADD_TODO: return { ...state, todos: [...state.todos, action.payload] }; case REMOVE_TODO: return { ...state, todos: state.todos.filter(todo => todo.id !== action.payload.id) }; default: return state; } } export default todoReducer;
Paso 3: Configurar el Store
// store.js import { createStore } from 'redux'; import todoReducer from './reducers'; const store = createStore(todoReducer); export default store;
Paso 4: Conectar React con Redux
// App.js import React from 'react'; import { Provider, useDispatch, useSelector } from 'react-redux'; import store from './store'; import { addTodoAction, removeTodoAction } from './actions'; function App() { const dispatch = useDispatch(); const todos = useSelector(state => state.todos); const addTodo = () => { const id = todos.length + 1; const text = `Tarea ${id}`; dispatch(addTodoAction(id, text)); }; const removeTodo = (id) => { dispatch(removeTodoAction(id)); }; return ( <div> <h1>Lista de Tareas</h1> <button onClick={addTodo}>Agregar Tarea</button> <ul> {todos.map(todo => ( <li key={todo.id}> {todo.text} <button onClick={() => removeTodo(todo.id)}>Eliminar</button> </li> ))} </ul> </div> ); } export default function Root() { return ( <Provider store={store}> <App /> </Provider> ); }
Ejercicio Práctico
Ejercicio
- Añade una nueva acción y un creador de acción para marcar una tarea como completada.
- Modifica el reductor para manejar esta nueva acción.
- Actualiza la interfaz de usuario para permitir marcar una tarea como completada.
Solución
Paso 1: Añadir la Acción y el Creador de Acción
// actions.js export const TOGGLE_TODO = 'TOGGLE_TODO'; export function toggleTodoAction(id) { return { type: TOGGLE_TODO, payload: { id } }; }
Paso 2: Modificar el Reductor
// reducers.js import { ADD_TODO, REMOVE_TODO, TOGGLE_TODO } from './actions'; const initialState = { todos: [] }; function todoReducer(state = initialState, action) { switch (action.type) { case ADD_TODO: return { ...state, todos: [...state.todos, { ...action.payload, completed: false }] }; case REMOVE_TODO: return { ...state, todos: state.todos.filter(todo => todo.id !== action.payload.id) }; case TOGGLE_TODO: return { ...state, todos: state.todos.map(todo => todo.id === action.payload.id ? { ...todo, completed: !todo.completed } : todo ) }; default: return state; } } export default todoReducer;
Paso 3: Actualizar la Interfaz de Usuario
// App.js import React from 'react'; import { Provider, useDispatch, useSelector } from 'react-redux'; import store from './store'; import { addTodoAction, removeTodoAction, toggleTodoAction } from './actions'; function App() { const dispatch = useDispatch(); const todos = useSelector(state => state.todos); const addTodo = () => { const id = todos.length + 1; const text = `Tarea ${id}`; dispatch(addTodoAction(id, text)); }; const removeTodo = (id) => { dispatch(removeTodoAction(id)); }; const toggleTodo = (id) => { dispatch(toggleTodoAction(id)); }; return ( <div> <h1>Lista de Tareas</h1> <button onClick={addTodo}>Agregar Tarea</button> <ul> {todos.map(todo => ( <li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} <button onClick={() => toggleTodo(todo.id)}>Completar</button> <button onClick={() => removeTodo(todo.id)}>Eliminar</button> </li> ))} </ul> </div> ); } export default function Root() { return ( <Provider store={store}> <App /> </Provider> ); }
Conclusión
En esta lección, hemos aprendido sobre las acciones y los reductores en Redux. Las acciones son objetos que describen eventos en la aplicación, mientras que los reductores son funciones puras que determinan cómo cambia el estado de la aplicación en respuesta a estas acciones. También hemos visto cómo combinar múltiples reductores y cómo conectar Redux con una aplicación de React. Con estos conocimientos, estás listo para gestionar el estado de aplicaciones más complejas utilizando Redux.
Curso de React
Módulo 1: Introducción a React
- ¿Qué es React?
- Configuración del Entorno de Desarrollo
- Hola Mundo en React
- JSX: Extensión de Sintaxis de JavaScript
Módulo 2: Componentes de React
- Entendiendo los Componentes
- Componentes Funcionales vs de Clase
- Props: Pasando Datos a Componentes
- State: Gestión del Estado del Componente
Módulo 3: Trabajando con Eventos
- Manejo de Eventos en React
- Renderizado Condicional
- Listas y Claves
- Formularios y Componentes Controlados
Módulo 4: Conceptos Avanzados de Componentes
- Elevando el Estado
- Composición vs Herencia
- Métodos del Ciclo de Vida de React
- Hooks: Introducción y Uso Básico
Módulo 5: Hooks de React
Módulo 6: Enrutamiento en React
Módulo 7: Gestión del Estado
- Introducción a la Gestión del Estado
- API de Contexto
- Redux: Introducción y Configuración
- Redux: Acciones y Reductores
- Redux: Conectando a React
Módulo 8: Optimización del Rendimiento
- Técnicas de Optimización del Rendimiento en React
- Memorización con React.memo
- Hooks useMemo y useCallback
- División de Código y Carga Perezosa
Módulo 9: Pruebas en React
- Introducción a las Pruebas
- Pruebas Unitarias con Jest
- Pruebas de Componentes con React Testing Library
- Pruebas de Extremo a Extremo con Cypress
Módulo 10: Temas Avanzados
- Renderizado del Lado del Servidor (SSR) con Next.js
- Generación de Sitios Estáticos (SSG) con Next.js
- TypeScript con React
- React Native: Creación de Aplicaciones Móviles