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.

const addTodoAction = {
  type: 'ADD_TODO',
  payload: {
    id: 1,
    text: 'Aprender Redux'
  }
};

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.

function addTodoAction(id, text) {
  return {
    type: 'ADD_TODO',
    payload: {
      id,
      text
    }
  };
}

¿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

  1. Añade una nueva acción y un creador de acción para marcar una tarea como completada.
  2. Modifica el reductor para manejar esta nueva acción.
  3. 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

Módulo 2: Componentes de React

Módulo 3: Trabajando con Eventos

Módulo 4: Conceptos Avanzados de Componentes

Módulo 5: Hooks de React

Módulo 6: Enrutamiento en React

Módulo 7: Gestión del Estado

Módulo 8: Optimización del Rendimiento

Módulo 9: Pruebas en React

Módulo 10: Temas Avanzados

Módulo 11: Proyecto: Construyendo una Aplicación Completa

© Copyright 2024. Todos los derechos reservados