14 de ago de 2018

Boas práticas na criação e separação de responsabilidades em componentes React

João Paulo Moraes

Uma das coisas mais emocionantes do React é o grande poder de reusabilidade de componentes, quando bem definidos. E se você já conhece um pouco sobre React ou pelo menos acessou sua página oficial, com certeza já deve ter se deparado com a seguinte frase: “Learn Once, Write Anywhere”. Traduzindo ao pé da letra: Aprenda uma vez, escreva em qualquer lugar.

Partindo desse mesmo princípio, é possível imaginarmos as coisas a partir de agora da seguinte forma: “Write Once, Use Anywhere”. Isso mesmo: escreva uma vez, use em qualquer lugar.

De forma geral, componentes React são compostos por lógica e apresentação, ou seja, fazem chamadas em APIs, manipulam dados e eventos e “também” criam os elementos para serem exibidos na interface do usuário dentro do método render.

import React, { Component } from 'react'
import './ListRepos.css'
class ListRepos extends Component {
 state = {
   repos: []
 }

 componentDidMount() {
   fetch('https://api.github.com/users/udacity/repos')
     .then(results => {
       return results.json()
     })
     .then(data =>
       this.setState({
         repos: data
       })
     )
 }

 render() {
   return (
     <div>
       {this.state.repos.map(repo => (
         <div key={repo.id}>
           <div className="title">{repo.name}</div>
           <div className="sub-title">{repo.language}</div>
         </div>
       ))}
     </div>
   )
 }
}

export default ListRepos

Nesse momento, nós temos um componente que funciona perfeitamente conforme o esperado, sem nenhum problema. Ele faz uma chamada na API do GitHub procurando por repositórios da Udacity, atribui os valores recebidos ao estado do componente e por fim exibe uma lista de repositórios juntamente com a linguagem de programação usada no projeto.

Agora imagine que você esteja trabalhando com outras pessoas nesse mesmo projeto e você pudesse dividir as responsabilidades em duas partes distintas, basicamente iterando entre a parte lógica e parte de apresentação.

Em React, nós temos um padrão simples e muito poderoso que nos permite separar ambas as responsabilidades, tornando nossos componentes mais reutilizáveis e fáceis de manter.

Leia também: 6 dicas para melhorar seu currículo de desenvolvedor web

Container e Presentation em React

Nesse padrão, nós podemos dividir nosso componente em duas partes menores, separando suas responsabilidades.

Container

  • Lidam com a parte lógica e o comportamento da aplicação
  • Fazem chamadas em APIs e manipulam seus dados
  • Definem manipuladores de eventos
  • São escritos como classes
  • Renderizam os componentes de apresentação

Presentation

  • Lidam com a aparência da aplicação
  • Recebem os dados de componentes proprietários/pais (containers) na forma de props
  • São escritos como componentes funcionais sem estado
  • Renderizam HTML e/ou outros componentes

Agora, se olharmos novamente para o nosso componente ListRepos, podemos então separar toda a parte lógica e comportamental da parte de apresentação.

import React, { Component } from 'react'
import ListRepos from './ListRepos'
class ListReposContainer extends Component {
 state = {
   repos: []
 }

 componentDidMount() {
   fetch('https://api.github.com/users/udacity/repos')
     .then(results => {
       return results.json()
     })
     .then(data =>
       this.setState({
         repos: data
       })
     )
 }

 render() {
   return (
     <ListRepos {...this.state} />
   )
 }
}
export default ListReposContainer

Primeiro, criamos um novo componente ListReposContainer, atribuímos a ele toda a parte lógica e passamos os dados do estado para um componente de apresentação (ListRepos) que irá se preocupar exclusivamente com a parte visual da aplicação e renderização dos dados.

import React from 'react'
import PropTypes from 'prop-types'
import './ListRepos.css'

const ListRepos = ({ repos }) => {
 return (
   <div>
     {repos.map(repo => (
       <div key={repo.id}>
         <div className="title">{repo.full_name}</div>
         <div className="sub-title">{repo.language}</div>
       </div>
     ))}
   </div>
 )
}

ListRepos.propTypes = {
 repos: PropTypes.array.isRequired
}

export default ListRepos

Conforme podemos observar, agora o nosso componente ListRepos foi refatorado para um componente funcional sem estado, simples e puro, que recebe uma lista de repos como propriedades de seu componente superior e retorna uma lista renderizada em seus elementos.

Agora, se voltarmos à nossa aplicação no navegador, vamos observar que seu comportamento permanece idêntico ao anterior, porém, com suas responsabilidades separadas em lógica e apresentação.

Como isso ajuda a reutilizar componentes?

Seguindo essa boa prática de container e presentation, nós conseguimos ter componentes mais independentes e que podem ser reutilizados de forma mais ampla. Se em outras partes do aplicativo for necessário exibir uma estrutura de dados semelhante, não seria necessário criar um novo componente – no máximo apenas necessário um novo container, que poderia carregar dados de um endpoint diferente ou até mesmo de outra API.

Pensando ainda em outros casos, demais desenvolvedores da equipe poderiam melhorar o container sem afetar a apresentação. A apresentação por sua vez, poderia ser melhorada ou até mesmo redesenhada com novos estilos pelo designer sem afetar a parte lógica.

Caso você queira testar esse exemplo sem gastar muito tempo ou ter que baixar e instalar as dependências do projeto, sugiro dar uma passadinha rápida no repl.it e ver as coisas acontecendo em tempo real. Você também é livre para fazer testes, implementações e o que mais achar interessante. Recomendo trocar o nome do usuário (udacity > google > microsoft > seu_usuário) da url no ListReposContainer para ver o que acontece.

Decidir o que manter no container e o que deixar por conta da apresentação nem sempre é uma tarefa fácil. Mas sempre que surgir dúvidas nesse sentido, recapitule os pontos destacados na lista acima e lembre-se que containers lidam com a parte lógica e manipulação dos dados, enquanto presentation apenas se preocupa com a apresentação da interface do usuário.

Leia também:

Sobre o autor
João Paulo Moraes

João Paulo Moraes é engenheiro de software & diretor criativo independente e atua como mentor e revisor de projetos na Udacity no programa Nanodegree Desenvolvedor React. Apaixonado por arte, tecnologia e fascinado pela simplicidade, tem como objetivo criar soluções simples e tornar experiências únicas.