Cómo funcionan los reductores Redux
Publicado: 2022-03-10state
, definitivamente se habrá encontrado con reductores. Este tutorial explicará el concepto de reductores y cómo funcionan específicamente en Redux.En este tutorial, vamos a aprender el concepto de reductores y cómo funcionan, específicamente en aplicaciones React. Para comprender y usar mejor Redux, es esencial una sólida comprensión de los reductores. Los reductores proporcionan una forma de actualizar el estado de una aplicación mediante una acción. Es una parte integral de la biblioteca Redux.
Este tutorial es para desarrolladores que desean obtener más información sobre Redux Reducers. Una comprensión de React y Redux sería beneficiosa. Al final del tutorial, debería tener una mejor comprensión del papel que juegan los reductores en Redux. Estaremos escribiendo demostraciones de código y una aplicación para comprender mejor los reductores y cómo afecta el estado en una aplicación.
¿Qué es un reductor?
Un Reducer es una función pura que toma el estado de una aplicación y una acción como argumentos y devuelve un nuevo estado. Por ejemplo, un reductor de autenticación puede tomar un estado inicial de una aplicación en forma de un objeto vacío y una acción que le indica que un usuario inició sesión y devolvió un nuevo estado de aplicación con un usuario que inició sesión.
Las funciones puras son funciones que no tienen efectos secundarios y devolverán los mismos resultados si se pasan los mismos argumentos.
A continuación se muestra un ejemplo de una función pura:
const add = (x, y) => x + y; add(2, 5);
El ejemplo anterior devuelve un valor basado en las entradas, si pasa 2
y 5
, siempre obtendrá 7
, siempre que sea la misma entrada, nada más afecta la salida que obtiene, ese es un ejemplo de una función pura.
A continuación se muestra un ejemplo de una función reductora que toma un estado y una acción.
const initialState = {}; const cartReducer = (state = initialState, action) => { // Do something here }
Definamos los dos parámetros que toma un reductor, state
y action
.
Estado
Un estado son los datos con los que están trabajando sus componentes: contiene los datos que requiere un componente y dicta lo que representa un componente. Una vez que un objeto de state
cambia, el componente se vuelve a renderizar. Si el estado de una aplicación es administrado por Redux, entonces el reductor es donde ocurren los cambios de estado.
Acción
Una acción es un objeto que contiene la carga útil de información. Son la única fuente de información para que se actualice la tienda Redux. Los reductores actualizan el almacén en función del valor de action.type
. Aquí definiremos action.type
como ADD_TO_CART
.
De acuerdo con la documentación oficial de Redux, las acciones son las únicas cosas que desencadenan cambios en una aplicación de Redux, contienen la carga útil para los cambios en una tienda de aplicaciones. Las acciones son objetos de JavaScript que le dicen a Redux el tipo de acción a realizar, generalmente se definen como funciones como la siguiente:
const action = { type: 'ADD_TO_CART', payload: { product: 'margarine', quantity: 4 } }
El código anterior es un valor de payload
típico que contiene lo que envía un usuario y se utilizará para actualizar el estado de la aplicación. Como puede ver desde arriba, el objeto de acción contiene el tipo de acción y un objeto de carga útil que sería necesario para que se realice esta acción en particular.
Actualización de estado mediante reductores
Para mostrar cómo funcionan los reductores, veamos el contador de números a continuación:
const increaseAction = { type: 'INCREASE', }; const decreaseAction = { type: 'DECREASE' }; const countReducer = (state = 0, action) => { switch(action.type){ case INCREASE: return state + 1; case DECREASE : return state -1; default: return state; } };
En el código anterior, el increaseAction
de la acción y la decreaseAction
de la acción son acciones utilizadas en el reductor para determinar a qué se actualiza el state
. A continuación, tenemos una función reductora llamada countReducer
, que toma una action
y un state
inicial cuyo valor es 0
. Si el valor de action.type
es INCREASE
, devolvemos un nuevo estado que se incrementa en 1, de lo contrario, si es DECREASE
, se devuelve un nuevo estado que se decrementa en 1. En los casos en los que no se quiere decir ninguna de esas condiciones, devolvemos state
.
Actualización de estado mediante reductores: el operador de propagación
El estado no se puede cambiar directamente, para crear o actualizar el estado, podemos usar el operador de propagación de JavaScript para asegurarnos de que no cambiamos el valor del estado directamente, sino que devolvemos un nuevo objeto que contiene un estado pasado y la carga útil del usuario.
const contactAction = { type: 'GET_CONTACT', payload: ['0801234567', '0901234567'] }; const initialState = { contacts: [], contact: {}, }; export default function (state = initialState, action) { switch (action.type) { case GET_CONTACTS: return { ...state, contacts: action.payload, }; default: return state; }
En el código anterior, estamos usando un operador de propagación para asegurarnos de que no cambiamos el valor del estado directamente, de esta manera podemos devolver un nuevo objeto que se llena con el estado que se le pasa y la carga útil que envía el usuario. Mediante el uso de un operador de propagación, podemos asegurarnos de que el estado se mantenga igual a medida que agregamos todos los elementos nuevos y también reemplazamos el campo de contactos en el estado si estaba presente antes.
Redux Reducers en acción: una demostración
Para comprender mejor los reductores de Redux y cómo funcionan, implementaremos una aplicación simple de búsqueda de detalles de películas, el código y la versión de trabajo se pueden encontrar aquí en Codesandbox. Para comenzar, vaya a su terminal e inicialice una aplicación de reacción usando el siguiente comando:
create-react-app movie-detail-finder
Una vez que nuestro proyecto se inicializó, vamos a instalar los paquetes que necesitaríamos para nuestra aplicación.
npm i axios reactstrap react-redux redux redux-thunk
Una vez que los paquetes estén instalados, iniciemos nuestro servidor de desarrollo usando el comando:
npm start
El comando anterior debería iniciar nuestro servidor de desarrollo de proyectos en nuestro navegador. A continuación, abramos nuestro proyecto en nuestro editor de texto de elección, dentro de nuestra carpeta src
del proyecto, elimine los siguientes archivos: App.css
, App.test.js
, serviceWorker.js
y setupTests.js
. A continuación, eliminemos todo el código que hace referencia a los archivos eliminados en nuestro App.js
Para este proyecto, usaremos la API Open Movie Database para obtener la información, el contenido y las imágenes de nuestra película para nuestra aplicación. Aquí hay un enlace a la API. Deberá registrarse y obtener las claves de acceso para poder usarla. aplicación, una vez que haya terminado, procedamos con nuestra aplicación mediante la creación de componentes.
Creación de componentes de aplicaciones
Primero, dentro de nuestra carpeta src
en nuestro directorio de proyectos, cree una carpeta llamada components y dentro de la carpeta, creemos dos carpetas llamadas Movie
y Searchbar
, nuestro componente debería verse como la imagen a continuación:
Componente de película de construcción
Construyamos el componente Movies
, que delineará la estructura de los detalles de la película que obtendremos de nuestra API. Para hacer esto, dentro de la carpeta Movies
de nuestro componente, cree un nuevo archivo Movie.js
, luego cree un componente basado en clases para los resultados de la API, hagámoslo a continuación.
import React, { Component } from 'react'; import { Card, CardImg, CardText, CardBody, ListGroup, ListGroupItem, Badge } from 'reactstrap'; import styles from './Movie.module.css'; class Movie extends Component{ render(){ if(this.props.movie){ return ( <div className={styles.Movie}> <h3 className="text-center my-4"> Movie Name: {this.props.movie.Title} </h3> <Card className="text-primary bg-dark"> <CardImg className={styles.Img} top src={this.props.movie.Poster} alt={this.props.movie.Title}/> <CardBody> <ListGroup className="bg-dark"> <ListGroupItem> <Badge color="primary">Actors:</Badge> {this.props.movie.Actors} </ListGroupItem> <ListGroupItem> <Badge color="primary">Genre:</Badge> {this.props.movie.Genre} </ListGroupItem> <ListGroupItem> <Badge color="primary">Year:</Badge> {this.props.movie.Year} </ListGroupItem> <ListGroupItem> <Badge color="primary">Writer(s):</Badge> {this.props.movie.Writer} </ListGroupItem> <ListGroupItem> <Badge color="primary">IMDB Rating:</Badge> {this.props.movie.imdbRating}/10 </ListGroupItem> </ListGroup> <CardText className="mt-3 text-white"> <Badge color="secondary">Plot:</Badge> {this.props.movie.Plot} </CardText> </CardBody> </Card> </div> ) } return null } } export default Movie;
En el código anterior, Uso de componentes del paquete reactstrap
, puede consultar la documentación aquí. Creamos un componente de tarjeta que incluye el nombre de la película, la imagen, el género, el actor, el año, el escritor de la película, la calificación y la trama. Para que sea más fácil pasar datos de este componente, construimos datos para que sirvan de apoyo a otros componentes. A continuación, construyamos nuestro componente Searchbar
.
Construyendo nuestro componente de barra de búsqueda
Nuestro componente Searchbar
contará con una barra de búsqueda y un componente de botón para buscar componentes de películas, hagámoslo a continuación:
import React from 'react'; import styles from './Searchbar.module.css'; import { connect } from 'react-redux'; import { fetchMovie } from '../../actions'; import Movie from '../Movie/Movie'; class Searchbar extends React.Component{ render(){ return( <div className={styles.Form}> <div> <form onSubmit={this.formHandler}> <input type="text" placeholder="Movie Title" onChange={e => this.setState({title: e.target.value})} value={this.state.title}/> <button type="submit">Search</button> </form> </div> <Movie movie={this.props.movie}/> </div> ) } }
En el código anterior, estamos importando la connect
desde react-redux
que se usa para conectar un componente React a la tienda Redux, proporciona al componente información de la tienda y también proporciona funciones que se usan para enviar acciones a la tienda. A continuación, importamos el componente Movie
y una función fetchMovie
from actions.
A continuación, tenemos una etiqueta de formulario con un cuadro de entrada para ingresar los títulos de nuestras películas, usando el setState
de React, agregamos un evento onChange
y un valor que establecerá el estado del title
en el valor ingresado en el cuadro de entrada. Tenemos una etiqueta de button
para buscar títulos de películas y usando el componente Movie
que importamos, pasamos las propiedades del componente como props
al resultado de la búsqueda.
Lo siguiente para nosotros es escribir una función para enviar el título de nuestra película a la API para enviarnos los resultados, también necesitamos establecer el estado inicial de la aplicación. hagamos eso abajo.
class Searchbar extends React.Component{ state = { title: '' } formHandler = (event) => { event.preventDefault(); this.props.fetchMovie(this.state.title); this.setState({title: ''}); }
Aquí, configuramos el estado inicial de la aplicación en cadenas vacías, creamos una función formHandler
que toma un parámetro de evento y pasa la función fetchMovie
de la acción y establece el título como el nuevo estado de la aplicación. Para completar nuestra aplicación, exportemos este componente usando la propiedad connect de react-redux
, para hacer esto usaríamos la propiedad mapToStateProps
de react redux para seleccionar la parte de los datos que nuestro componente necesitaría, puede obtener más información sobre mapToStateProps
aquí.
const mapStateToProps = (state) => { return { movie: state.movie } } export default connect(mapStateToProps, { fetchMovie })(Searchbar)
Agreguemos estilos a nuestro formulario creando un archivo Searchbar.module.css
y agregando los estilos a continuación:
.Form{ margin: 3rem auto; width: 80%; height: 100%; } input{ display: block; height: 45px; border: none; width: 100%; border-radius: 0.5rem; outline: none; padding: 0 1rem; } input:focus, select:focus{ border: 2px rgb(16, 204, 179) solid; } .Form button{ display: block; background: rgb(16, 204, 179); padding: 0.7rem; border-radius: 0.5rem; width: 20%; margin-top: 0.7rem; color: #FFF; border: none; text-decoration: none; transition: all 0.5s; } button:hover{ opacity: 0.6; } @media(max-width: 700px){ input{ height: 40px; padding: 0 1rem; } .Form button{ width: 40%; padding: 0.6rem; } }
Una vez que hayamos hecho lo anterior, nuestro componente de barra de búsqueda debería verse similar a la imagen a continuación:
Creación de acciones para la aplicación
En este componente, configuraremos las acciones de Redux para nuestra aplicación. Primero, dentro del directorio src
, cree una carpeta llamada actions
y dentro de la carpeta, crearíamos un archivo index.js
. Aquí crearíamos una función fetchMovie
que toma un parámetro de título y obtiene la película de la API usando Axios. Hagamos esto a continuación:
import axios from 'axios'; export const fetchMovie = (title) => async (dispatch) => { const response = await axios.get( `https://cors-anywhere.herokuapp.com/https://www.omdbapi.com/?t=${title}&apikey=APIKEY`); dispatch({ type: 'FETCH_MOVIE', payload: response.data }) }
En el código anterior, axios
y creamos una función llamada fetchMovie
que toma un parámetro de title
usando async/await para que podamos realizar una solicitud al servidor API. Tenemos una función de dispatch
que envía a Redux el objeto de acción que se le pasa. De lo que tenemos arriba, estamos enviando una acción con el tipo FETCH_MOVIE
y la carga útil que contiene la respuesta que obtuvimos de la API.
NOTA: La apikey
en la solicitud se reemplazará con su propia apikey
después de registrarse en OmdbAPI .
Creación de reductores de aplicaciones
En esta sección, vamos a crear reductores para nuestra aplicación.
const fetchMovieReducer = (state = null, action) => { switch(action.type){ case 'FETCH_MOVIE': return action.payload; default: return state; } } const rootReducer = (state, action) => { return { movie: fetchMovieReducer(state, action) } } export default rootReducer;
En el código anterior, creamos un fetchMovieReducer
que toma un estado predeterminado de null
y un parámetro de action
, usando un operador de cambio, para el caso FETCH_MOVIE
, devolveremos el valor de action.payload
que es la película que obtuvimos de la API. Si la acción que intentamos realizar no está en el reductor, regresamos a nuestro estado predeterminado.
A continuación, creamos una función rootReducer
que aceptará el estado actual y una acción como entrada y devolverá fetchMovieReducer
.
Poniendo todo junto
En esta sección, terminaríamos nuestra aplicación creando nuestra tienda redux en index.js
, hagámoslo a continuación:
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import App from './App'; import 'bootstrap/dist/css/bootstrap.min.css'; import './index.css'; import reducers from './reducers'; const store = createStore(reducers, applyMiddleware(thunk)) ReactDOM.render( <Provider store={store}> <> <App/> </> </Provider>, document.getElementById('root') )
En el código anterior, creamos la store
de aplicaciones usando el método createStore
pasando el reductor que creamos y un middleware. Los middlewares son complementos que nos permiten potenciar las funcionalidades de Redux. Aquí estamos haciendo uso del middleware Redux Thunk usando applyMiddleware
. El middleware Redux Thunk es necesario para que nuestra tienda realice actualizaciones asincrónicas. Esto es necesario porque, de forma predeterminada, Redux actualiza la tienda de forma síncrona.
Para asegurarnos de que nuestra aplicación conozca la tienda exacta que debe usar, envolvimos nuestra aplicación en un componente de Provider
y pasamos la tienda como accesorio. Al hacer esto, otros componentes de nuestra aplicación pueden conectarse y compartir información con la tienda.
Agreguemos un poco de estilo a nuestro archivo index.css
.
*{ margin: 0; padding: 0; box-sizing: border-box; } body{ background: rgb(15, 10, 34); color: #FFF; height: 100vh; max-width: 100%; }
Representación y prueba de un buscador de detalles de películas
En esta sección, vamos a concluir nuestra aplicación mediante la representación de nuestra aplicación en nuestro App.js
, para hacer esto, vamos a crear un componente basado en clases llamado App
e inicializar nuestra barra de búsqueda y campo de entrada.
import React from 'react'; import Searchbar from './components/Searchbar/Searchbar'; import styles from './App.module.css'; class App extends React.Component{ render(){ return( <div className={styles.App}> <h1 className={styles.Title}>Movies Search App</h1> <Searchbar/> </div> ) } } export default App;
Aquí, creamos un componente basado en la clase App con un h1
que dice Movie Search App y agregamos nuestro componente Searchbar
. Nuestra aplicación debería parecerse a la siguiente imagen:
Una demostración de trabajo está disponible en Codesandbox.
Conclusión
Los reductores son una parte importante de la gestión del estado de Redux, con los reductores podemos escribir funciones puras para actualizar áreas específicas de nuestras aplicaciones Redux sin efectos secundarios. Hemos aprendido los conceptos básicos de los reductores de Redux, sus usos y el concepto central de los reductores, el estado y los argumentos.
Puede llevar esto más lejos al ver la documentación sobre los reductores de Redux aquí. Puede llevar esto más lejos y construir más sobre los reductores de Redux, hágame saber lo que construye.
Recursos
- Documentación de React-Redux
- Documentación redux
- función
connect()
- Función
applyMiddleware