En esta parte de la traducción del tutorial React, hablaremos sobre la arquitectura de las aplicaciones React. En particular, discutiremos el popular patrón Contenedor / Componente.

→
Parte 1: descripción general del curso, razones de la popularidad de React, ReactDOM y JSX→
Parte 2: componentes funcionales→
Parte 3: archivos de componentes, estructura del proyecto→
Parte 4: componentes principales y secundarios→
Parte 5: inicio del trabajo en una aplicación TODO, los fundamentos del estilo→
Parte 6: sobre algunas características del curso, JSX y JavaScript→
Parte 7: estilos en línea→
Parte 8: trabajo continuo en la aplicación TODO, familiaridad con las propiedades de los componentes→
Parte 9: propiedades del componente→
Parte 10: Taller sobre trabajo con propiedades de componentes y estilo→
Parte 11: generación de marcado dinámico y método de matrices de mapas→
Parte 12: taller, tercera etapa de trabajo en una aplicación TODO→
Parte 13: componentes basados en clases→
Parte 14: taller sobre componentes basados en la clase, estado de los componentes.→
Parte 15: talleres de componentes de salud→
Parte 16: la cuarta etapa de trabajo en una aplicación TODO, manejo de eventos→
Parte 17: quinta etapa de trabajo en una aplicación TODO, modificando el estado de los componentes→
Parte 18: la sexta etapa de trabajo en una aplicación TODO→
Parte 19: métodos del ciclo de vida de los componentes.Parte 20: la primera lección de representación condicional→
Parte 21: segunda lección y taller sobre representación condicional→
Parte 22: la séptima etapa de trabajo en una aplicación TODO, descargando datos de fuentes externas→
Parte 23: primera lección sobre trabajar con formularios→
Parte 24: Segunda lección de formularios→
Parte 25: Taller sobre trabajo con formularios→
Parte 26: arquitectura de la aplicación, patrón de contenedor / componente→
Parte 27: proyecto del cursoLección 44. Arquitectura de aplicación, patrón de contenedor / componente
→
OriginalA veces, la cantidad de trabajo de la que es responsable un componente separado es demasiado grande; un componente tiene que resolver demasiadas tareas. El uso del patrón Contenedor / Componente le permite separar la lógica de la aplicación de la lógica de la formación de su representación visual. Esto le permite mejorar la estructura de la aplicación, para compartir la responsabilidad del desempeño de varias tareas entre diferentes componentes.
En la lección práctica anterior, creamos un componente enorme cuya longitud de código se acerca a las 150 líneas. Aquí está el código que obtuvimos entonces:
import React, {Component} from "react" class App extends Component { constructor() { super() this.state = { firstName: "", lastName: "", age: "", gender: "", destination: "", isVegan: false, isKosher: false, isLactoseFree: false } this.handleChange = this.handleChange.bind(this) } handleChange(event) { const {name, value, type, checked} = event.target type === "checkbox" ? this.setState({ [name]: checked }) : this.setState({ [name]: value }) } render() { return ( <main> <form> <input name="firstName" value={this.state.firstName} onChange={this.handleChange} placeholder="First Name" /> <br /> <input name="lastName" value={this.state.lastName} onChange={this.handleChange} placeholder="Last Name" /> <br /> <input name="age" value={this.state.age} onChange={this.handleChange} placeholder="Age" /> <br /> <label> <input type="radio" name="gender" value="male" checked={this.state.gender === "male"} onChange={this.handleChange} /> Male </label> <br /> <label> <input type="radio" name="gender" value="female" checked={this.state.gender === "female"} onChange={this.handleChange} /> Female </label> <br /> <select value={this.state.destination} name="destination" onChange={this.handleChange} > <option value="">-- Please Choose a destination --</option> <option value="germany">Germany</option> <option value="norway">Norway</option> <option value="north pole">North Pole</option> <option value="south pole">South Pole</option> </select> <br /> <label> <input type="checkbox" name="isVegan" onChange={this.handleChange} checked={this.state.isVegan} /> Vegan? </label> <br /> <label> <input type="checkbox" name="isKosher" onChange={this.handleChange} checked={this.state.isKosher} /> Kosher? </label> <br /> <label> <input type="checkbox" name="isLactoseFree" onChange={this.handleChange} checked={this.state.isLactoseFree} /> Lactose Free? </label> <br /> <button>Submit</button> </form> <hr /> <h2><font color="#3AC1EF">Entered information:</font></h2> <p>Your name: {this.state.firstName} {this.state.lastName}</p> <p>Your age: {this.state.age}</p> <p>Your gender: {this.state.gender}</p> <p>Your destination: {this.state.destination}</p> <p>Your dietary restrictions:</p> <p>Vegan: {this.state.isVegan ? "Yes" : "No"}</p> <p>Kosher: {this.state.isKosher ? "Yes" : "No"}</p> <p>Lactose Free: {this.state.isLactoseFree ? "Yes" : "No"}</p> </main> ) } } export default App
El primer inconveniente de este código, que inmediatamente llama la atención, es que al trabajar con él, debe desplazarse constantemente por la ventana del editor.
Puede notar que la mayor parte de este código es la lógica de formar la interfaz de la aplicación, el contenido del método
render()
. Además, una cierta cantidad de código es responsable de inicializar el estado del componente. El componente también tiene lo que se llama "lógica de negocios" (es decir, lo que implementa la lógica del funcionamiento de la aplicación). Este es el código del método
handleChange()
.
Según los resultados de algunos estudios, se sabe que la capacidad de un programador para percibir el código que está viendo se ve muy afectada si el código es lo suficientemente largo, y el programador tiene que usar el desplazamiento para verlo en su totalidad. Me di cuenta de esto durante las clases. Cuando el código del que estoy hablando resulta ser bastante largo, y constantemente tengo que desplazarme por él, se hace más difícil para los estudiantes percibirlo.
Sería bueno si reelaboramos nuestro código, compartiendo la responsabilidad entre los diferentes componentes para la formación de la interfaz de la aplicación (lo que ahora se describe en el método
render()
) y para la implementación de la lógica de la aplicación, es decir, por definición de cómo debería verse interfaz (el código correspondiente ahora está representado por el constructor del componente en el que se inicializa el estado y el controlador de eventos de control
handleChange()
). Al utilizar este enfoque para el diseño de aplicaciones, de hecho, trabajamos con dos tipos de componentes, y debe tenerse en cuenta que puede encontrar nombres diferentes para dichos componentes.
Usaremos el patrón Contenedor / Componente aquí. Al usarlo, las aplicaciones se crean dividiendo los componentes en dos tipos: componentes componentes (la palabra Contenedor en el nombre del patrón se refiere a ellos) y componentes de presentación (este es Componente en el nombre del patrón). A veces, los componentes del contenedor se denominan componentes "inteligentes", o simplemente "contenedores", y los componentes de presentación se denominan componentes "tontos", o simplemente "componentes". Hay otros nombres para este tipo de componentes y, debe tenerse en cuenta, el significado que se incluye en estos nombres puede, de un caso a otro, diferir en ciertas características. En general, la idea general de este enfoque es que tenemos un componente contenedor que es responsable de almacenar el estado y contiene métodos para administrar el estado, y la lógica de formar la interfaz se transfiere a otro componente de presentación. Este componente solo es responsable de recibir las propiedades del componente contenedor y de la correcta formación de la interfaz.
→
Aquí está el material de Dan Abramov en el que explora esta idea.
Transformamos el código de nuestra aplicación de acuerdo con el patrón Contenedor / Componente.
Primero, prestemos atención al hecho de que ahora todo en la aplicación está ensamblado en un solo componente de la
App
. Esta aplicación está diseñada para simplificar su estructura tanto como sea posible, pero en proyectos reales, el componente de la
App
apenas tiene sentido transferir la tarea de presentar el formulario e incluir código diseñado para organizar el trabajo de los mecanismos internos de este formulario.
Agregue a la misma carpeta en la que se encuentra el archivo
Form.js
, el archivo
Form.js
, en el que se
Form.js
el código del nuevo componente. Transferimos todo el código del componente de la
App
a este archivo, y convertimos el componente de la
App
, que ahora está representado por el componente basado en la clase, en un componente funcional, cuya tarea principal será la salida del componente de
Form
. No olvide importar el componente
Form
en el componente
App
. Como resultado, el código del componente de la
App
se verá así:
import React, {Component} from "react" import Form from "./Form" function App() { return ( <Form /> ) } export default App
Esto es lo que muestra la aplicación en la pantalla en esta etapa del trabajo.
Aplicación en navegadorEn clases anteriores, le dije que prefiero que el componente de la
App
sea algo así como una "tabla de contenido" de la aplicación, que indica en qué orden se muestran sus secciones en la página, representadas por otros componentes que son tareas delegadas de renderizar grandes fragmentos de la aplicación.
Hemos mejorado ligeramente la estructura de la aplicación, pero el problema principal, expresado en el hecho de que un componente tiene demasiada responsabilidad, aún no se ha resuelto. Simplemente transferimos todo lo que estaba anteriormente en el componente
App
componente
Form
. Por lo tanto, ahora vamos a resolver este problema. Para hacer esto, cree, en la misma carpeta en la que se encuentran los archivos
Form.js
y
App.js
, otro archivo:
FormComponent.js
. Este archivo representará el componente de presentación responsable de la visualización del formulario. De hecho, puede nombrarlo de manera diferente, puede estructurar archivos de componentes de manera diferente, todo depende de las necesidades y la escala de un proyecto en particular. El archivo
Form.js
contendrá la lógica del formulario, es decir, el código del componente contenedor. Por lo tanto,
FormContainer.js
nombre a
FormContainer.js
y cambie el comando de importación en el código del componente de la
App
, llevándolo a este formulario:
import Form from "./FormContainer"
También puede cambiar el nombre del componente
Form
a
FormContainer
, pero no haremos esto. Ahora transferiremos el código responsable de representar el formulario desde el archivo
FormComponent.js
archivo
FormComponent.js
.
El componente
FormComponent
será funcional. Así es como se verá su código en esta etapa del trabajo:
function FormComponent(props) { return ( <main> <form> <input name="firstName" value={this.state.firstName} onChange={this.handleChange} placeholder="First Name" /> <br /> <input name="lastName" value={this.state.lastName} onChange={this.handleChange} placeholder="Last Name" /> <br /> <input name="age" value={this.state.age} onChange={this.handleChange} placeholder="Age" /> <br /> <label> <input type="radio" name="gender" value="male" checked={this.state.gender === "male"} onChange={this.handleChange} /> Male </label> <br /> <label> <input type="radio" name="gender" value="female" checked={this.state.gender === "female"} onChange={this.handleChange} /> Female </label> <br /> <select value={this.state.destination} name="destination" onChange={this.handleChange} > <option value="">-- Please Choose a destination --</option> <option value="germany">Germany</option> <option value="norway">Norway</option> <option value="north pole">North Pole</option> <option value="south pole">South Pole</option> </select> <br /> <label> <input type="checkbox" name="isVegan" onChange={this.handleChange} checked={this.state.isVegan} /> Vegan? </label> <br /> <label> <input type="checkbox" name="isKosher" onChange={this.handleChange} checked={this.state.isKosher} /> Kosher? </label> <br /> <label> <input type="checkbox" name="isLactoseFree" onChange={this.handleChange} checked={this.state.isLactoseFree} /> Lactose Free? </label> <br /> <button>Submit</button> </form> <hr /> <h2><font color="#3AC1EF">Entered information:</font></h2> <p>Your name: {this.state.firstName} {this.state.lastName}</p> <p>Your age: {this.state.age}</p> <p>Your gender: {this.state.gender}</p> <p>Your destination: {this.state.destination}</p> <p>Your dietary restrictions:</p> <p>Vegan: {this.state.isVegan ? "Yes" : "No"}</p> <p>Kosher: {this.state.isKosher ? "Yes" : "No"}</p> <p>Lactose Free: {this.state.isLactoseFree ? "Yes" : "No"}</p> </main> ) }
Si observa este código, queda claro que no podemos limitarnos a simplemente transferirlo de un archivo a otro, ya que ahora hay enlaces al estado (por ejemplo,
this.state.firstName
) y al controlador de eventos (
this.handleChange
), que solía estar en el mismo componente según la clase en la que se encontraba este código de representación. Ahora, todo lo que se tomó previamente de la misma clase en la que se encontraba el código de representación se tomará de las propiedades pasadas al componente. Hay algunos otros problemas. Ahora arreglaremos este código, pero primero volveremos al código del componente
Form
, que ahora está en el archivo
FormContainer.js
.
Su método
render()
ahora está vacío. Necesitamos que el componente
FormComponent
se muestre en este método y debemos organizar la transferencia de las propiedades necesarias. Importamos el
FormComponent
en el archivo
Form
y mostramos el
FormComponent
en el método
render()
, pasándole un controlador de eventos y, como objeto, estado. Ahora el código del componente
Form
se verá así:
import React, {Component} from "react" import FormComponent from "./FormComponent" class Form extends Component { constructor() { super() this.state = { firstName: "", lastName: "", age: "", gender: "", destination: "", isVegan: false, isKosher: false, isLactoseFree: false } this.handleChange = this.handleChange.bind(this) } handleChange(event) { const {name, value, type, checked} = event.target type === "checkbox" ? this.setState({ [name]: checked }) : this.setState({ [name]: value }) } render() { return( <FormComponent handleChange={this.handleChange} data={this.state} /> ) } } export default Form
FormComponent
código del componente
FormComponent
, llevándolo al siguiente formulario:
import React from "react" function FormComponent(props) { return ( <main> <form> <input name="firstName" value={props.data.firstName} onChange={props.handleChange} placeholder="First Name" /> <br /> <input name="lastName" value={props.data.lastName} onChange={props.handleChange} placeholder="Last Name" /> <br /> <input name="age" value={props.data.age} onChange={props.handleChange} placeholder="Age" /> <br /> <label> <input type="radio" name="gender" value="male" checked={props.data.gender === "male"} onChange={props.handleChange} /> Male </label> <br /> <label> <input type="radio" name="gender" value="female" checked={props.data.gender === "female"} onChange={props.handleChange} /> Female </label> <br /> <select value={props.data.destination} name="destination" onChange={props.handleChange} > <option value="">-- Please Choose a destination --</option> <option value="germany">Germany</option> <option value="norway">Norway</option> <option value="north pole">North Pole</option> <option value="south pole">South Pole</option> </select> <br /> <label> <input type="checkbox" name="isVegan" onChange={props.handleChange} checked={props.data.isVegan} /> Vegan? </label> <br /> <label> <input type="checkbox" name="isKosher" onChange={props.handleChange} checked={props.data.isKosher} /> Kosher? </label> <br /> <label> <input type="checkbox" name="isLactoseFree" onChange={props.handleChange} checked={props.data.isLactoseFree} /> Lactose Free? </label> <br /> <button>Submit</button> </form> <hr /> <h2><font color="#3AC1EF">Entered information:</font></h2> <p>Your name: {props.data.firstName} {props.data.lastName}</p> <p>Your age: {props.data.age}</p> <p>Your gender: {props.data.gender}</p> <p>Your destination: {props.data.destination}</p> <p>Your dietary restrictions:</p> <p>Vegan: {props.data.isVegan ? "Yes" : "No"}</p> <p>Kosher: {props.data.isKosher ? "Yes" : "No"}</p> <p>Lactose Free: {props.data.isLactoseFree ? "Yes" : "No"}</p> </main> ) } export default FormComponent
Aquí arreglamos el código teniendo en cuenta el hecho de que el componente ahora recibe datos y un enlace al controlador de eventos a través de propiedades.
Después de todas estas transformaciones, ni la apariencia del formulario ni la forma en que funciona cambiarán, pero hemos mejorado la estructura del código del proyecto, aunque el tamaño del código del componente
FormComponent
todavía es bastante grande. Sin embargo, ahora este código resuelve solo un problema, solo es responsable de la visualización del formulario. Por lo tanto, trabajar con él ahora es mucho más fácil.
Como resultado, hemos logrado una separación de responsabilidades entre los componentes. El componente
Form
del archivo
FormContainer.js
ahora
FormContainer.js
ocupado exclusivamente por la lógica de la aplicación, y el componente
FormComponent.js
archivo
FormComponent.js
contiene solo el código que forma la interfaz de la aplicación. El componente de la
App
ahora solo es responsable de ensamblar la página a partir de bloques grandes.
Vale la pena señalar que, dada la existencia de bibliotecas como
Redux
y la API de
Context
lanzada recientemente, el patrón de Contenedor / Componente discutido aquí ya no es tan relevante como antes. Por ejemplo, Redux puede soportar el estado global de una aplicación que los componentes pueden usar.
Resumen
En esta lección, examinamos el uso del patrón Contenedor / Componente, cuyo objetivo es dividir los componentes en los responsables de la formación de la interfaz de la aplicación y los responsables del almacenamiento del estado y la lógica de la aplicación. La aplicación de este patrón ayuda a mejorar la estructura del código de las aplicaciones React y facilita el proceso de desarrollo. La próxima vez trabajaremos en un proyecto de curso.
Estimados lectores! ¿Qué patrones de diseño utiliza al desarrollar aplicaciones React?
