Nesta parte da tradução do tutorial do React, falaremos sobre a arquitetura dos aplicativos React. Em particular, discutiremos o popular padrão Container / Component.

→
Parte 1: visão geral do curso, razões para a popularidade do React, ReactDOM e JSX→
Parte 2: componentes funcionais→
Parte 3: arquivos de componentes, estrutura do projeto→
Parte 4: componentes pai e filho→
Parte 5: início do trabalho em um aplicativo TODO, noções básicas de estilo→
Parte 6: sobre alguns recursos do curso, JSX e JavaScript→
Parte 7: estilos embutidos→
Parte 8: trabalho contínuo na aplicação TODO, familiaridade com as propriedades dos componentes→
Parte 9: propriedades do componente→
Parte 10: Workshop sobre como trabalhar com propriedades e estilo de componentes→
Parte 11: geração dinâmica de marcação e método de matrizes de mapas→
Parte 12: workshop, terceira etapa do trabalho em uma aplicação TODO→
Parte 13: componentes baseados em classe→
Parte 14: workshop sobre componentes baseados em classe, status dos componentes→
Parte 15: oficinas de saúde componentes→
Parte 16: a quarta etapa do trabalho em um aplicativo TODO, manipulação de eventos→
Parte 17: quinta etapa do trabalho em um aplicativo TODO, modificando o estado dos componentes→
Parte 18: a sexta etapa do trabalho em um aplicativo TODO→
Parte 19: métodos do ciclo de vida dos componentesParte 20: a primeira lição sobre renderização condicional→
Parte 21: segunda lição e workshop sobre renderização condicional→
Parte 22: sétima etapa do trabalho em um aplicativo TODO, baixando dados de fontes externas→
Parte 23: primeira lição sobre como trabalhar com formulários→
Parte 24: Segunda lição sobre formulários→
Parte 25: Workshop sobre como trabalhar com formulários→
Parte 26: arquitetura do aplicativo, padrão Container / Component→
Parte 27: projeto do cursoLição 44. Arquitetura de Aplicativos, Padrão de Container / Componente
→
OriginalÀs vezes, a quantidade de trabalho pela qual um componente separado é responsável é muito grande; um componente precisa resolver muitas tarefas. O uso do padrão Container / Component permite separar a lógica do aplicativo da lógica da formação de sua representação visual. Isso permite que você melhore a estrutura do aplicativo, compartilhe a responsabilidade pelo desempenho de várias tarefas entre diferentes componentes.
Na lição prática anterior, criamos um enorme componente cujo comprimento de código se aproxima de 150 linhas. Aqui está o código que temos então:
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
A primeira desvantagem desse código, que imediatamente chama sua atenção, é que, ao trabalhar com ele, você constantemente precisa rolar o código na janela do editor.
Você pode perceber que a maior parte desse código é a lógica da formação da interface do aplicativo, o conteúdo do método
render()
. Além disso, uma certa quantidade de código é responsável pela inicialização do estado do componente. O componente também possui o que é chamado de "lógica de negócios" (ou seja, o que implementa a lógica do funcionamento do aplicativo). Este é o código do método
handleChange()
.
De acordo com os resultados de alguns estudos, sabe-se que a capacidade de um programador de perceber o código que ele está vendo é muito prejudicada se o código for longo o suficiente, e o programador precisa usar a rolagem para visualizá-lo por inteiro. Eu notei isso durante as aulas. Quando o código que estou falando acaba sendo bastante longo, e eu constantemente tenho que percorrê-lo, fica mais difícil para os alunos percebê-lo.
Seria bom se refizéssemos nosso código, compartilhando a responsabilidade entre os diferentes componentes para a formação da interface do aplicativo (o que agora é descrito no método
render()
) e para a implementação da lógica do aplicativo, ou seja, por definição de como ele deve parecer interface (o código correspondente agora é representado pelo construtor do componente no qual o estado é inicializado e pelo manipulador de eventos de controle
handleChange()
). Ao usar essa abordagem para o design do aplicativo, de fato, trabalhamos com dois tipos de componentes e deve-se observar que você pode encontrar nomes diferentes para esses componentes.
Usaremos o padrão Container / Component aqui. Ao usá-lo, os aplicativos são construídos dividindo os componentes em dois tipos - componentes (a palavra Container no nome do padrão se refere a eles) e componentes de apresentação (este é Component no nome do padrão). Às vezes, os componentes do contêiner são chamados de componentes "inteligentes", ou simplesmente "contêineres", e os componentes de apresentação são chamados de componentes "burros", ou simplesmente "componentes". Existem outros nomes para esses tipos de componentes e, note-se, o significado contido nesses nomes pode, caso a caso, diferir em determinados recursos. Em geral, a idéia geral dessa abordagem é que temos um componente de contêiner responsável por armazenar estados e contendo métodos para gerenciar estados, e a lógica de formar a interface é transferida para outro componente de apresentação. Este componente é responsável apenas por receber propriedades do componente de contêiner e pela formação correta da interface.
→
Aqui está o material de Dan Abramov no qual ele explora essa idéia.
Transformamos o código de nossa aplicação de acordo com o padrão Container / Component.
Primeiro, vamos prestar atenção ao fato de que agora tudo no aplicativo é montado em um único componente do
App
. Esse aplicativo foi projetado de maneira a simplificar sua estrutura o máximo possível, mas em projetos reais o componente
App
dificilmente transfere a tarefa de renderizar o formulário e incluir nele o código destinado à organização do trabalho dos mecanismos internos desse formulário.
Adicione à mesma pasta em que o arquivo
App.js
está localizado, o arquivo
Form.js
, no qual o código do novo componente estará localizado. Transferimos todo o código do componente
App
para este arquivo e convertemos o componente
App
, que agora é representado por um componente baseado na classe, em um componente funcional, cuja principal tarefa será a saída do componente
Form
. Não se esqueça de importar o componente
Form
para o componente
App
. Como resultado, o código do componente
App
ficará assim:
import React, {Component} from "react" import Form from "./Form" function App() { return ( <Form /> ) } export default App
Aqui está o que o aplicativo exibe na tela nesta fase do trabalho.
Aplicativo no navegadorNas aulas anteriores, eu disse a você que prefiro que o componente
App
seja algo como um "índice" do aplicativo, que indica em que ordem suas seções são exibidas na página, representadas por outros componentes aos quais são delegadas tarefas de renderização de grandes fragmentos do aplicativo.
Melhoramos um pouco a estrutura do aplicativo, mas o principal problema, expresso no fato de que um componente tem muita responsabilidade, ainda não foi resolvido. Simplesmente transferimos tudo o que havia anteriormente no componente
App
para o componente
Form
. Portanto, agora vamos resolver esse problema. Para fazer isso, crie, na mesma pasta em que os arquivos
Form.js
e
App.js
, outro arquivo -
FormComponent.js
. Este arquivo representará o componente de apresentação responsável pela visualização do formulário. De fato, você pode nomear de maneira diferente, pode estruturar arquivos de componentes de maneira diferente, tudo depende das necessidades e da escala de um projeto específico. O arquivo
Form.js
conterá a lógica do formulário, ou seja, o código do componente do contêiner. Portanto, renomeie-o para
FormContainer.js
e altere o comando import no código do componente
App
, trazendo-o para este formulário:
import Form from "./FormContainer"
Você também pode renomear o componente
Form
para
FormContainer
, mas não faremos isso. Agora, transferiremos o código responsável pela renderização do formulário do arquivo
FormContainer.js
para o arquivo
FormComponent.js
.
O componente
FormComponent
será funcional. Aqui está como o código dele ficará nesta etapa do trabalho:
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> ) }
Se você observar esse código, fica claro que não podemos nos limitar a simplesmente transferi-lo de arquivo para arquivo, já que agora existem links para o estado (por exemplo,
this.state.firstName
) e o manipulador de eventos (
this.handleChange
), que costumava estar no mesmo componente com base na classe em que esse código de renderização estava. Agora, tudo o que foi obtido anteriormente da mesma classe em que o código de renderização estava localizado será obtido das propriedades passadas para o componente. Existem outros problemas. Agora vamos corrigir esse código, mas primeiro retornaremos ao código do componente
Form
, que está agora no arquivo
FormContainer.js
.
Seu método
render()
agora está vazio. Precisamos que o componente
FormComponent
seja exibido neste método e precisamos organizar a transferência das propriedades necessárias para ele. Importamos o
FormComponent
para o arquivo
Form
e exibimos o
FormComponent
no método
render()
, passando um manipulador de eventos e, como objeto, estado. Agora, o código do componente
Form
ficará assim:
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
Vamos
FormComponent
código do componente
FormComponent
, trazendo-o para o seguinte formulário:
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
Aqui, corrigimos o código levando em consideração o fato de que o componente agora recebe dados e um link para o manipulador de eventos por meio de propriedades.
Depois de todas essas transformações, nem a aparência do formulário nem a maneira como ele funciona serão alteradas, mas melhoramos a estrutura do código do projeto, embora o tamanho do código do componente
FormComponent
ainda seja bastante grande. No entanto, agora esse código resolve apenas um problema, ele é responsável apenas pela visualização do formulário. Portanto, trabalhar com ele agora é muito mais fácil.
Como resultado, conseguimos uma separação de responsabilidades entre os componentes. O componente
Form
do arquivo
FormContainer.js
agora
FormContainer.js
ocupado exclusivamente pela lógica do aplicativo, e o componente
FormComponent.js
arquivo
FormComponent.js
contém apenas o código que forma a interface do aplicativo. O componente
App
agora é responsável apenas pela montagem da página a partir de blocos grandes.
Vale ressaltar que, dada a existência de bibliotecas como
Redux
e a API de
Context
recentemente lançada, o padrão Container / Componente discutido aqui não é mais tão relevante quanto antes. Por exemplo, o Redux pode suportar o estado global de um aplicativo que os componentes podem usar.
Sumário
Nesta lição, examinamos o uso do padrão Container / Component, cujo objetivo é dividir os componentes nos responsáveis pela formação da interface do aplicativo e naqueles responsáveis pelo armazenamento do estado e pela lógica do aplicativo. A aplicação desse padrão ajuda a melhorar a estrutura de código dos aplicativos React e facilita o processo de desenvolvimento. Da próxima vez, trabalharemos em um projeto de curso.
Caros leitores! Quais padrões de design você usa ao desenvolver aplicativos React?
