Tutorial de React Parte 26: Arquitectura de aplicación, patrón de contenedor / componente

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.

imagen

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 curso

Lección 44. Arquitectura de aplicación, patrón de contenedor / componente


Original

A 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 navegador

En 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?

Source: https://habr.com/ru/post/446206/


All Articles