Comenzando Con Redux: Conectando Redux con React
Spanish (Español) translation by Rafael Chavarría (you can also view the original English article)
Esta es la tercera parte de la serie sobre Comenzando con Redux y en este tutorial, vamos a aprender cómo conectar un almacén Redux con React. Redux es una librería independiente que funciona con todas las librerías front-end populares y marcos de trabajo. Y funciona muy bien con React debido a su aproximación funcional.
No necesitas haber seguido las partes anteriores de esta serie para que este tutorial tenga sentido. Si estás aquí para aprender sobre cómo usar React y Redux, puedes tomar una Recapitulación Rápida abajo y después revisar el código de la parte anterior y comenzar desde ahí.
Racapitulación Rápida
En el primer tutorial, aprendimos sobre el flujo de trabajo de Redux y respondimos la pregunta, ¿Por qué Redux?. Creamos una aplicación demo muy básica y te mostramos cómo los distintos componentes de Redux---acciones, reductores, y el almacén--- están conectados.
En la publicación anterior, comenzamos una aplicación de lista de contacto que te permite agregar contactos y después los muestra como una lista. Un almacén Redux fue creado para nuestra lista de contactos y agregamos unos cuantos reductores y acciones. Intentamos enviar acciones y recuperar el nuevo estado usando métodos de almacenamiento como store.dispatch()
y store.getState()
.
Al final de este artículo, aprenderás
- la diferencia entre componentes de contenedor y componentes de presentación
- sobre la librería react-redux
- cómo unir react y redux usando
connect()
- cómo enviar acciones usando
mapDispatchToProps
- cómo recuperar estado usando
mapStateToProps
El código para el tutorial está disponible en GitHub en el repositorio react-redux-demo. Toma el código de la rama v2 y usa eso como un punto de partida para este tutorial. Si tienes curiosidad de saber cómo luce la aplicación al final de este tutorial, prueba la rama v3. Comencemos.
Diseñando una Jerarquía de Componente: Componente Inteligente vs. Tonto
Este es un concepto que probablemente escuchaste antes. Pero echemos un vistazo rápido a la diferencia entre componentes inteligentes y tontos. Recuerda que creamos dos directorios separados para componentes, uno llamado containers/, y el otro components/. El beneficio de esta aproximación es que la lógica de comportamiento está separada de la vista.
Los componentes de presentación se conocen como tontos porque están preocupados por cómo lucen las cosas. Están desacoplados de la lógica de negocio de la aplicación y reciben datos y llamadas de un componente padre exclusivamente vía accesorios. No les importa si tu aplicación está conectada a un almacén Redux si los datos provienen del estado local del componente padre.
Los componentes contenedores, por el otro lado, lidian con la parte de comportamiento y deberían contener marcado DOM muy limitado y estilo. Ellos pasan los datos que necesitan ser generados a los componentes tontos como accesorios.
Cubrí el tema a fondo en otro tutorial, Componentes Declarativos vs. Sin declatación en React.
Continuando, veamos cómo vamos a organizar nuestros componentes.



Componentes de Presentación
Aquí están los componentes de presentación que estaremos usando en este tutorial.
components/AddContactForm.jsx
import React from 'react'; const AddContactForm = ({onInputChange, onFormSubmit}) => ( <form> <div className="form-group"> <label htmlFor="emailAddress">Email address</label> <input type="email" class="form-control" name="email" onChange={onInputChange} placeholder="name@example.com" /> </div> {/* Some code omitted for brevity */} <div className="form-group"> <label htmlFor="physicalAddress">Address</label> <textarea className="form-control" name="address" onChange={onInputChange} rows="3"></textarea> </div> <button type="submit" onClick={onFormSubmit} class="btn btn-primary"> Submit </button> </form> ) export default AddContactForm;
Este es un formulario HTML para agregar un nuevo contacto. El componente recibe llamadas onInputChange
y onFormSubmit
como accesorios. El evento onInputChange
es disparado cuando el valor de entrada cambia y onFormSubmit
cuando la forma está siendo enviada.
components/ContactList.jsx
const ContactList = (props) => { return( <ul className="list-group" id="contact-list"> {props.contactList.map( (contact) => <li key={contact.email} className="list-group-item"> <ContactCard contact = {contact}/> </li> )} </ul>) } export default ContactList;
Este componente recibe un arreglo de objetos de contacto como accesorios y por lo tanto el nombre ContactList. Usamos el método Array.map()
para extraer detalles individuales de contacto y después pasamos esos datos a <ContactCard />
.
components/ContactCard.jsx
const ContactCard = ({contact}) => { return( <div> <div className="col-xs-4 col-sm-3"> {contact.photo !== undefined ? <img src={contact.photo} alt={contact.name} className="img-fluid rounded-circle" /> : <img src="img/profile_img.png" alt ={contact.name} className="img-fluid rounded-circle" />} </div> <div className="col-xs-8 col-sm-9"> <span className="name">{contact.name + ' ' + contact.surname}</span><br/> {/* Some code omitted for brevity */} </div> </div> ) } export default ContactCard;
Este componente recibe un objeto de contacto y muestra el nombre e imagen del contacto. Para aplicaciones prácticas, podría tener sentido hospedar imágenes JavaScript en la nube.
Componentes Contenedor
También vamos a construir componentes contenedor esqueleto.
containers/Contacts.jsx
class Contacts extends Component { constructor(props) { super(props); this.returnContactList = this.returnContactList.bind(this); } returnContactList() { // Retrieve contactlist from the store } render() { return ( <div> <AddContact/> <br /> <ContactList contactList= {this.returnContactList()} /> </div> ); } } export default Contacts;
La función returnContactList()
recupera el arreglo de objetos de contacto y lo pasa al componente ContactList. Ya que returnContactList()
devuelve la información del almacén, dejaremos esa lógica en blanco por el momento.
containers/AddContacts.jsx
class AddContact extends Component { constructor(props) { super(props); /* Function binding goes here. Omitted for brevity */ } showAddContactBox() { /* Logic for toggling ContactForm */ } handleInputChange(event) { const target = event.target; const value = target.value; const name = target.name; /* Logic for handling Input Change */ } handleSubmit(e) { e.preventDefault(); /* Logic for hiding the form and update the state */ } /* Renders the AddContactForm */ renderForm() { return( <div className="col-sm-8 offset-sm-2"> <AddContactForm onFormSubmit={this.handleSubmit} onInputChange={this.handleInputChange} /> </div> ) } render() { return( <div> { /* A conditional statement goes here that checks whether the form should be displayed or not */} </div> ) } } export default AddContact;
Hemos creado tres esqueletos de métodos manejadores que corresponden a las tres acciones. Todos ellos envían acciones para actualizar el estado. En el método de generación, hemos dejado fuera la lógica para mostrar/ocultar el formulario porque necesitamos recuperar el estado.
Ahora veamos cómo unir react y redux
La librería react-redux.
Las uniones React no están disponibles en Redux por defecto. Necesitarás instalar una librería extra llamada react-redux primero.
npm install --save react-redux
La librería exporta solo dos APIs que necesitarás recordar, un componente <Provider />
y una función de orden superior conocida como connect()
.
El Componente Provider
Librerías como Redux necesitan hacer accesibles los datos de almacenamiento a todo el árbol de componente React desde el componente raíz. El patrón Provider permite a la librería pasar datos de arriba a abajo. El código de abajo demuestra cómo Provider agrega mágicamente el estado a todos los componentes en el árbol de componentes.
Código Demo
import { Provider } from 'react-redux' ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
La aplicación entera necesita tener acceso al almacén. Así qué, envolvemos al proveedor alrededor del componente de aplicación y después agregamos los datos que necesitamos al contexto del árbol. Los descendientes del componente entonces tienen acceso a los datos.
El Método connect()
Ahora que hemos provisto el almacenamiento a nuestra aplicación, necesitamos conectar React al almacén. La única forma en que puedes comunicarte con el almacén es enviar acciones y recuperando el estado. Previamente usamos store.dispatch()
para enviar acciones y store.getState()
para recuperar la última captura del estado. El connect()
te permite hacer exactamente esto, pero con la ayuda de dos métodos conocidos como mapDispatchToProps
y mapStateToProps
. He demostrado este concepto en el ejemplo de abajo:
Código Demo
import {connect} from 'react-redux' const AddContact = ({newContact, addContact}) => { return ( <div> {newContact.name} <br /> {newContact.email} <br /> {newContact.phone} <br /> Are you sure you want to add this contact? <span onClick={addContact}> Yes </span> </div> ) } const mapStateToProps = state => { return { newContact : state.contacts.newContact } } const mapDispatchToProps = dispatch => { return { addContact : () => dispatch(addContact()) } } export default connect( mapStateToProps, mapDispatchToProps )(AddContact)
mapStateProps
y mapDispatchToProps
devuelven un objeto y la llave de este objeto se vuelve un accesorio del componente conectado. Por ejemplo, state.contacts.newContact
está mapeado a props.newContact
. El creador de acción addContact()
está mapeado a props.addContact
.
Pero para que esto funciones, necesitas la última línea en el código de arriba.
export default connect( mapStateToProps, mapDispatchToProps )(AddContact)
En vez de exportar el componente AddContact
directamente, estamos exportando un componente conectado. El connect provee addContact
y newContact
como accesorios al componente <AddContact/>
.
¿Cómo Conectar React y Redux?
Después, vamos a cubrir los pasos que necesitas seguir para conectar React y Redux.
Instala la Librería react-redux.
Instala la librería react-redux si aún no lo has hecho. Puedes usar NPM o Yarn para instalarla.
npm install react-redux --save
Proporciona el Almacén para tu Componente App
Crea el almacén primero. Después, haz accesible al objeto de almacén a tu árbol de componentes pasándolo como un accesorio a <Provider/>
.
index.js
import React from 'react'; import {render}from 'react-dom'; import { Provider } from 'react-redux' import App from './App'; import configureStore from './store' const store = configureStore(); render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
Conecta Contenedores React a Redux
La función connect es usada para unir contenedor React a Redux. Lo que significa es que puedes usar la característica de conexión para:
- suscribir al almacén y mapear su estado a tus accesorios
- enviar acciones y mapear el envío de llamadas a tus accesorios
Una vez que has conectado tu aplicación a Redux, puedes usar this.props
para acceder al estado actual y también para enviar acciones. Voy a demostrar el proceso sobre el componente AddContact
. AddContact
necesita enviar tres acciones y obtener el estado de dos propiedades desde el almacén. Echemos un vistazo al código.
Primero, importa connect
a AddContact.jsx.
import { connect } from 'react-redux';
Segundo, crea dos métodos mapStateToProps
y mapDispatchToProps
.
function mapStateToProps(state) { return { isHidden : state.ui.isAddContactFormHidden, newContact: state.contacts.newContact } } function mapDispatchToProps(dispatch) { return { onFormSubmit: (newContact) => { dispatch(addContact(newContact)); }, onInputChange: (name,value) => { dispatch(handleInputChange(name,value)); }, onToggle: () => { dispatch(toggleContactForm()); } } }
mapStateToProps
recibe el estado del almacén como un argumento. Este devuelve un objeto que describe cómo el estado del almacén es mapeado a tus accesorios. mapDispatchToProps
devuelve un objeto similar que describe como las acciones de envío son mapeadas a tus accesorios.
Finalmente, usamos connect
para unir el componente AddContact
a las dos funciones como sigue:
export default connect(mapStateToProps, mapDispatchToProps) (AddContact)
Actualiza los Componentes de Contenedor para Usar los Accesorios
Los accesorios del componente ahora están equipados para leer estado del almacén y enviar acciones. La lógica para handleInputChange
,handleSubmit
y showAddContactBox
debería ser actualizada como sigue:
showAddContactBox() { const { onToggle } = this.props; onToggle(); } handleInputChange(event) { const target = event.target; const value = target.value; const name = target.name; const { onInputChange } = this.props; onInputChange(name,value); } handleSubmit(e) { e.preventDefault(); this.props.onToggle(); this.props.onFormSubmit(); }
Hemos definido los métodos manejadores. Pero aún falta una parte---la declaración condicional dentro de la función render
.
render() { return( <div> { this.props.isHidden === false ? this.renderForm(): <button onClick={this.showAddContactBox} className="btn"> Add Contact </button>} </div> ) }
Si isHidden
es falso, el formulario es generado. De otro modo, se genera un botón.
Mostrando Los Contactos
Hemos completado la parte más retadora. Ahora, todo lo que queda es mostrar estos contactos como una lista. El contenedor Contacts
es el mejor lugar para esa lógica.
import React, { Component } from 'react'; import { connect } from 'react-redux'; /* Component import omitted for brevity */ class Contact extends Component { constructor(props) { super(props); this.returnContactList = this.returnContactList.bind(this); } returnContactList() { return this.props.contactList; } render() { return ( <div> <br /> <AddContact/> <br /> <ContactList contactList= {this.returnContactList()} /> </div> ); } } function mapStateToProps(state) { return { contactList : state.contacts.contactList, } } export default connect(mapStateToProps, null) (Contact);
Hemos recorrido el mismo procedimiento que seguimos arriba para conectar el componente Contacts con el almacén Redux. La función mapStateToProps
mapea el objeto de almacén a los accesorios contactList
. Cuando usamos connect para unir el valor de los accesorios al componente Contact, el segundo argumento para el connect es null porque no tenemos ninguna acción para ser enviada. Eso completa la integración de nuestra app con el estado del almacén Redux.
¿Qué Sigue?
En el siguiente artículo, echaremos un vistazo más a fondo a los middlewares y enviar acciones que involucran recuperar datos desde el servidor. ¡Comparte tus ideas en los comentarios!