DEV Community

loading...

Refatoração de código React

Carlos Kazuo
Steve Aoki of Frontend
・15 min read

Trago neste artigo alguns aprendizados acumulados durante minha carreira. É importante lembrar que apesar dos exemplos serem um pouco generalizados são voltados para utilização dentro de aplicações que utilizem componentes em React e que por isso supõe-se que o leitor tenha algum conhecimento sobre a biblioteca e também sobre ECMAScript.

Também adiciono que podem ter pontos tanto básicos da linguagem quanto algo avançado.

E meu português para artigos continua coloquial e com alguns erros de sintaxe que possam gerar confusão.


Esse é um assunto recorrente para todo programador e em qualquer outra linguagem e em qualquer era da computação.

Contos da meia noite

(em fast forward)

Para contar uma história resumida ao extremo sobre como surgiram padrões de projeto, Our whole universe was in a hot, dense state... dois programadores: Kent Beck e Ward Cunningham pegaram as idéias de um arquiteto chamado Christopher Alexander e tentaram portá-las para o mundo da criação de Software.

fonte: Wikipedia

Depois vieram os Gang of Four, 4 outros autores, e lançaram um livro:

Design Patterns

Trata-se de um compilado de padrões de projeto para programação orientada a objetos e que foi base para toda a popularização sobre o tema, tal que foi portado e adaptado para todas as outras linguagens.

E este é só um storytelling para dizer que este problema pode ser mais antigo o quanto você imagina e que a dor está em todos os lugares.

Tá, mas e o React?

Falando da lib em si, a própria documentação tem uma parte falando sobre o tema, explicando como foi tracejado sua concepção. Recomendo a leitura para entender mais como foram as idéias por trás de sua criação.

Tendo em mente tais idéias, antes de apresentar a minha lista gostaria de evidenciar alguns ideais de desenvolvimentos que serão pertinentes para o decorrer deste artigo:

Se não tiver com pressa, busque sobre o assunto no Google mesmo, depois volte aqui :D

Bom senso

Creio que acima de tudo, vale o bom senso. Talvez do próprio desenvolvedor (ou seja, você) ou mesmo em acordo de time. O padrão de desenvolvimento adotado na concepção do produto deve ser um princípio na criação do produto. Um ótimo exemplo é no link lá em cima dos princípios de design do React, o importante é o projeto ter consistência.

Não fará nenhum sentido colocar 20 design patterns enquanto você cita 10 nomes de pessoas importantes no desenvolvimento de Software se ninguém mais do seu time comprar a idéia. Seu código não tornará mais legível se quem ler seu código não entender onde você quer chegar.

Minha listinha

Essa lista não é bem uma lista, mas sim algumas releituras de documentações e alguns exemplos de melhorias de código que fui acumulando durante minha carreira.

Também aponto uma outra fonte de padrões já existentes: https://reactpatterns.com/

Citarei alguns padrões existentes na lista deles porém com outro viés.

Também gostaria de explicar algumas idéias básicas para os iniciantes que estão acessando esse artigo, então se algum item você achar básico demais pode pular.

JS(X)?

(jeiésséquis is too sexy)

O JSX é uma extensão de sintaxe JavaScript inspirada no XML, famoso "tag HTML dentro do seu JS". Coloco ele como uma primeira idéia a discutir pois ainda vejo muita galera que está começando com React achá-lo estranho. E é mesmo.

Porém gostaria de dismistificá-lo. O JSX, tal como explicado na documentação do React, diz-se "uma extensão de sintaxe para JavaScript". E realmente é. Apesar de popularizado pelo React, tem muito mais a ver com o Babel, que é uma ferramenta que compila seu código ECMAScript para uma versão que pode ser interpretada pelos navegadores.

ECMAScript é a mesma coisa que JavaScript, só que sem direitos autorais

Ou seja, quando você ver esse código:

function Component ({ children, ...props }) {
  return <div {...props}>{children}</div>
}

Se trata apenas de ECMAScript.

function Component ({ children, ...props }) {
  return React.createElement('div', props, children)
}

Se não acredita em mim, teste você mesmo! Escrevendo sem JSX você consegue usar React direto no navegador, já que o mesmo não entende código JSX.

"Ah mas eu uso classes".

Classes no ES6 são apenas syntax sugar de funções e prototipagem. Brinque um pouco com o REPL do Babel e veja como seria seu código para navegadores que não suportam ES6.

Darei outro exemplo abaixo, utilizando um componente dentro de outro:

function OutroComponente ({ children, ...props }) {
  return <div {...props}>{children}</div>
}

function Component ({ children, ...props }) {
  return <OutroComponente {...props}>{children}</OutroComponente>
}

O Babel transforma o acima no abaixo.

function OutroComponente ({ children, ...props }) {
  return React.createElement("div", props, children);
}

function Component ({ children, ...props }) {
  return React.createElement(OutroComponente, props, children);
}

No final é tudo função. - Guarde bem essa afirmação.

Render wat

wat

No React meu componente pode retornar um componente ou uma composição de componentes.

function Component () {
  return <OtroComponente />
}

Mas lembre também que seu componente pode retornar também outros tipos:

Strings

function Component () {
  return "Oi leitor!"
}

Null

function NullComponent () {
  return null
}

(na verdade ele não renderiza nada visual)

Array

function ArrayComponent () {
  return [
    'Repetindo ',
    9,
    <NullComponent key='null' />,
    <StringComponent key='string' />
  ]
}

// Renderiza: "Repetindo 9Oi leitor!"

(Após o React v16.2)

É importante ressaltar que é necessário o atributo key e que assim funciona da mesma forma que a maioria dos reacteiros estão acostumados a enfrentar laços de repetição:

function Component ({ items }) {
  return (
    <div>
      {
        items.map(({id, value}) =>
          <span key={`item-{id}`}>{value}</span>)
      }
    </div>
  )
}

edit: por favor, não utilize o índice do array como atributo key. Obrigado Stefano por me lembrar.

Seria como imprimir um vetor de componentes:

function Component (...) {
  return (
    <div>
      {[
        <span key={`item-1`}>{item1}</span>,
        <span key={`item-2`}>{item2}</span>,
        <span key={`item-3`}>{item3}</span>,
        {/* ... */}
      ]}
    </div>
  )
}

Fragments

Também na versão do React v16.2 vieram os fragmentos. A idéia é bem similar aos DocumentFragment já existente no DOM.

function Component () {
  return (
    <React.Fragment>
      <li>hum</li>
      <li>dovs</li>
      <li>trevs</li>
    </React.Fragment>
  )
}

Utilizando o Babel após a versão 7 também é possível utilizar um shorthand:

function Component () {
  return (
    <>
      <li>hum</li>
      <li>dovs</li>
      <li>trevs</li>
    </>
  )
}

Link do repl do Babel pra você brincar.

Declaração de função: explícita ou Arrow Function

Nota do autor: essa é uma posição pessoal. Fique livre em discordar e não utilizar.

Para componentes React minha preferência é para funções explícitas quando não utilizar HOC ou algum "gerador" de componente. Um exemplo claro de função retornando componentes são styled components:

const Button = styled.button`
  /* ... */
`

Mas ...Por que?

Prefiro funções explícitas pois, por legibilidade, é mais fácil encontrar uma função no meio de diversas outras quando está declarada explicitamente que uma no meio de diversas consts.

Também historicamente o React DevTools não pegava o nome do componente a não ser que estivesse sido declarado utilizando função explícita. Hoje a história é outra. O DevTools consegue (nem sempre, discutirei mais a frente) pegar o nome do componente sem problemas.

Arrow Functions são anônimas, o que dependendo do problema chega-se a outro padrão:

.displayName

Use quando necessário. Fica difícil depurar código quando o DevTools mostra toda uma árvore de Anonymous Components. Não é nem questão de escrever ou não utilizando arrow function, adicionar .displayName não mata gatinhos e vai auxiliar bastante mesmo para diminuir a quantidade de styled(AlgumaCoisa) que pode prejudicar a visualização.

E vai aparecer bonito na stacktrace

Smart vs Dumb Components

(no geral) Dumb Components são fáceis de mockar, testar e inclusive de visualizar no Storybook

Separe sempre que possível seus componentes. Curto e utilizo bastante o Storybook. Com ele é possível deixar um componente fullscreen e inclusive utilizar uma view inteira da sua aplicação, mockando seus dados caso ela seja burra e testar a responsividade da página inteira utilizando o Chrome DevTools.

.defaultProps

Há duas formas para atribuir valores padrão para as props em Componentes Funcionais:

function Greeting({ name = 'Kazuo' }) {
  return <div>Hi {name}!</div>
}

e

function Greeting({ name }) {
  return <div>Hi {name}!</div>
}

Greeting.defaultProps = {
  name: 'Kazuo'
}

Prefira o primeiro formato exceto no caso que descreverei abaixo:

shouldComponentUpdate()

Quando fazíamos componentes usando classes, havia uma forma de otimizar a renderização e deixar explícito para o React quando é para renderizar novamente ou não dependendo de alguns fatores previamente configurados. Esta ferramenta é o shouldComponentUpdate.

class MyComponent extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    // props
    if (this.props.name !== nextProps.name) {
      return false
    }

    // state
    if (this.state.name !== nextState.name) {
      return false
    }
  }

  render () {
    // ...
  }
}

Em componentes criados a partir de classes é possível utilizá-lo para controlar a renderização somente quando há alteração de props ou do state e inclusive dizer quais mudanças o componente deveria ativamente ouvir para renderizar ou não o componente.

Ainda sobre componentes de classes, também existe um shorthand que faz uma checagem shallow (raza):

class MyComponent extends PureComponent {
  render () {
    // ...
  }
}

A comparação raza é feita da seguinte forma: ele verifica se algum ítem de props ou do state alteraram no primeiro nível dos objetos. Trabalhando com imutabilidade dá pra gerenciar bem quando o componente deve renderizar.

E pra Componentes Funcionais?

O React.memo é algo novo (veio na versão 16.6) e veio exatamente para auxiliar neste problema, só tem um detalhe: funciona apenas para props (pois estados utilizando Hooks são executados dentro do próprio render do componente, não sendo possível uma interceptação do dado pelo React.memo). Abaixo um exemplo retirado da documentação.

function MyComponent(props) {
  /* renderize usando props */
}

function areEqual(prevProps, nextProps) {
  /*
  se prevProps e nextProps renderizam o mesmo resultado,
  retorne true.
  caso contrário, retorne false.
  */
}

export default React.memo(MyComponent, areEqual);

A maneira que o React lida com estado de componentes funcionais foi introduzida na versão 16.8 com a vinda dos Hooks.

Como os Hooks são executados dentro da própria função de render, o React.memo não consegue manusear por fora do componente o que acontece dentro dele.

E aí que vem o ganho de utilizar .defaultProps fora dos argumentos do React:

function Greeting({ name }) {
  return <div>Hi {name}!</div>
}

Greeting.defaultProps = {
  name: 'Kazuo'
}

const MemoGreeting = React.memo(Greeting)

Você pode deixar ítens no .defaultProps e eles ficam disponíveis para comparação na função do React.memo. Quando você deixa default props dentro da chamada dos argumentos da função, ele sempre será executado somente após o React.memo, não sendo possível efetuar a comparação.

Prop Types com Spread de atributos

(Se você usa TypeScript (ou Flow), pule uma casa.)

No React Patterns tem um exemplo bem bacana, mas gostaria muito de compartilhar um fato quando define-se propTypes no JavaScript. Pense no exemplo a seguir:

function Greeting({ name, ...restProps }) {
  return <MyDiv {...restProps}>Hi {name}!</MyDiv>;
}

Greeting.propTypes = {
  name: PropTypes.string.isRequired
}

O mais comum é deixar que o componente MyDiv tenha sua validação de Prop Types. Porém se em algum momento você precisar validar no seu componente coisas que já são validadas nos Prop Types do componente filho, lembre que o .propTypes é um objeto.

Greeting.propTypes = {
  ...MyDiv.propTypes,
  name: PropTypes.string.isRequired
}

Você pode estender os seus Prop Types com qualquer outro objeto de Prop Types.

Condicionais

Ter condicionais no meio do JSX é um anti-pattern bem eficaz, muito utilizado e amplamente difundido por reacters. É normal encontrar um short-circuit evaluation ou mesmo um ternariozinho aqui ou lá e retirar esses condicionais torna o código verboso por provavelmente precisar criar mais Componentes. Porém creio que todos que já codaram React também já viram códigos com uma superutilização delas.

function Component ({ vaiChover, diaDeBanho, calor }) {
  return (
    <div>
      {!vaiChover && diaDeBanho && calor && <Chuveiro />}
    </div>
  )
}

Nestes casos o que recomendo como ação básica imediata é: extraia a lógica de condicionais para uma variável a parte:

function Component ({ vaiChover, diaDeBanho, calor }) {
  const shouldDisplayChuveiro = !vaiChover && diaDeBanho && calor
  return (
    <>
      {/* ... */}
      {shouldDisplayChuveiro && <Chuveiro />}
      {/* ... */}
    </>
  )
}

Melhora a legibilidade? Nem tanto.

Porém em alguns casos é possível jogar essa responsabilidade pro componente filho (claro, quando fizer sentido essa responsabilidade ser repassada para ele). Lembra que você pode fazer seu componente retornar null e não renderizar conteúdo?

function Chuveiro ({ vaiChover, diaDeBanho, calor }) {
  if (vaiChover) return null
  if (!diaDeBanho) return null
  if (!calor) return null

  return 🚿
}

function Component (props) {
  return (
    <>
      {/* ... */}
      <Chuveiro {...props} />
      {/* ... */}
    </>
  )
}

Proxy

O site do React Patterns tem um ótimo exemplo simples com um botão:

<button type="button">

// Ergo Proxy
const Button = props =>
  <button type="button" {...props} />

Mas gostaria de estender essa idéia. Na verdade, tá mais para um hack. No React você grosseiramente pode utilizar componentes com ponto (.). Um exemplo da própria documentação do React é a Context API.

const MyContext = React.createContext(defaultValue)

// Provider
<MyContext.Provider value={/* some value */}>

// Consumer
<MyContext.Consumer>
  {value => /* renderiza algo baseado no valor do context */}
</MyContext.Consumer>

Ok, eu menti sobre usar componentes contendo ponto no nome, tecnicamente não é o correto. Mas sim, você pode utilizar componentes como atributos de objetos. Isso possibilita a criação de certos componentes "escopando" sua utilização, como por exemplo:

import MyBanner from 'path/to/MyBanner'

<MyBanner>
  <MyBanner.CloseButton>Mensagem a11y pro botão de fechar</MyBanner.CloseButton>
  <MyBanner.Image
    src="https://..."
    alt="mensagem descritiva sobre a imagem" />
  <MyBanner.Text>Conteúdo pro meu banner</MyBanner.Text>
  <MyBanner.Cta onClick={handleOnClick}>E aqui o call-to-action</MyBanner.Cta>
</MyBanner>

E o arquivo do meu componente seria algo como:

// MyBanner.js

const MyBanner = styled.div`...`

MyBanner.CloseButton = styled.button`...`
MyBanner.Image = styled.img`...`
MyBanner.Text = styled.p`...`
MyBanner.Cta = styled.button`...`

export default MyBanner

Claro que esse padrão é longe de ser algo ideal, eu diria que é muito mais situacional: quando não há um controle sobre criação de componentes que sejam reutilizáveis ou para algo muito específico que vale ter tal escopo.

Ainda ressalto que o ideal e mais recomendável seria utilizar componentes já existentes:

import MyBanner from 'path/to/MyBanner'

<MyBanner>
  <CloseButton>Mensagem a11y pro botão de fechar</CloseButton>
  <Image
    src="https://..."
    alt="mensagem descritiva sobre a imagem" />
  <Text>Conteúdo pro meu banner</Text>
  <Button onClick={handleOnClick}>E aqui o call-to-action</Button>
</MyBanner>

E Hooks?

Creio que este é um assunto que vale uma nova postagem só para este assunto e acho que já me estendi em vários outros aspectos.

Mas tudo que falo para componentes vale para funções ;D

Quando refatorar?

Queria usar etimologia para lembrar que sempre dizemos refatorar e nunca fatorar. Ou seja, antes de tudo: faça funcionar. E exatamente por fazer funcionar, ninguém vai fazer um código a la John Carmack de primeira. Também muitos de nós enfrentamos entregas, não há como fugir.

Faça com calma e coloque testes. Lembre daquele ciclo do TDD e aplique: red-green-refactor. Faça o teste para quebrar; faça funcionar e então faça direito. Ou mesmo:

Faça funcionar, faça direito, faça-o executar rapidamente. - Kent Beck

Por que refatorar?

Mesmo com a correria de prazos, refatorar o código de uma maneira que utilize padrões já conhecidos ou mesmo sem utilizá-los já é um ganho quando bem executado. Se o produto tem a necessidade de ter futuras manutenções e ser escalável, é necessário ter um código de fácil legibilidade e sem muito atrito para modificações e melhorias.

Ainda jogo uma analogia com algo da nossa área: para otimizar a rapidez de leitura geralmente bancos de dados gastam mais tempo na parte da escrita para deixá-lo com certa ordenação para facilitar no resgate do dado.

Creio que para código podemos ter algo semelhante: perca um tempo para escrever um código que fique fácil de dar manutenção pois quando for preciso e chegar a hora da manutenção, terá menos problemas e vai ser mais rápido fazer qualquer alteração.

Como refatorar?

Eu indico Refactoring, do Martin Fowler e também o Clean Code, do Uncle Bob.

Os pontos abaixo são bem explorados pelos livros e creio que com estas 2 dicas sensacionalistas resumidas você irá conseguir escrever seu código tão desejado:

Formatação

Atente-se à formatação do teu código. No Clean Code há partes onde explicam tanto formatação horizontal quanto formatação vertical.

Em qualquer idioma, ler parágrafos grandes prejudica a leitura de qualquer conteúdo editorial. Da mesma forma, ter código pouco espaçado ou com muito scroll acaba virando um pesadelo para entender seu funcionamento.

Escreva seu código como uma carta de amor para outro desenvolvedor

Levem na brincadeira a afirmação porém escrevam código para que outros desenvolvedores entendam, você inclusive. E não estou dizendo de encher seu código de comentários que muitas vezes podem ser redundantes. Deixe o seu código semântico, não importando se é HTML, JS ou qualquer outra linguagem, e que seja fácil de ser lido e com APIs de fácil utilização. Faça com que seu componente seja fácil de ser utilizado e entendido. Faça uma boa documentação e de fácil manutenção/atualização (ex: Storybook) e automatize o que for possível (ex: documentar as PropTypes direto da declaração de interface do TypeScript).

Algo que vejo crescer nos últimos anos é o termo Developer Experience (DX). Mesmo se seu código não for aberto, ao escrever código que possa ser lido até por uma criança de 5 anos (não literalmente, pfv), isso pode facilitar pra você mesmo quando tiver de dar manutenção 5 anos depois, lembrando zero em relação ao conhecimento que você possui no momento da escrita do código.

Estrutura

Para estruturar arquivos e código existem diversos padrões. Antes de tudo: dê preferência pro padrão já existente, ainda mais se for um padrão já adotado pelo time.

Existem diversos padrões: ducks, Clean Architecture...

Pessoalmente gosto de algo mais livre com uma pegada mais funcional e um pouco de DDD. Caso também seja o teu perfil, recomendo bastante a estrutura do Dan Abramov:

https://react-file-structure.surge.sh/

E eu estenderia também para código, tal como John Carmack sugere. Como citado anteriormente: Se teu módulo (arquivos em ESM) começar a ficar grande, quebre em mais arquivos.

Estrutura bonus

Tente também não criar uma estrutura de arquivos muito aninhada. Tente sempre deixar o mais flat possível, ou seja, evite diretórios dentro de diretórios virar uma árvore gigante. Tente sempre deixar o mais perto possível da raiz do pacote/projeto e quebre mais os seus componentes e código se começar a aninhar muito.

Se seu projeto for um monorepo, extraia/quebre funcionalidades em módulos/pacotes. "Make each program do one thing well". Quebre mais seu código e faça com que seus módulos sejam pequenos e que façam apenas uma coisa e bem. Isso também facilitará na hora de trocar um módulo por outro e facilita também na hora de criar testes.

E não se esqueça

caloi

Use tanto testes quanto estilo de código (Coding Style) e ferramentas de automatização ao seu favor. Faça interfaces que facilitem o uso de componentes, "Não me faça pensar".

livro

Quanto mais abstraído teu código e fácil de entender, mais rápido será para fazer alterações, dar manutenção e adicionar funcionalidades.

Conclusão

Desenvolvimento de Software ainda é uma disciplina bem artesanal. Mesmo tendo automações, ainda é necessário escrever código para que programas, sites e aplicativos funcionem. Ainda não é acessível ter automatizado algo que nos cuspa códigos, pior ainda para frontend que ainda temos que além de pensar em fazer um bom código e tanto seu código quanto a saída pro usuário devem ser semânticas, leves e rápidas para rodar em celulares e em leitores de tela por exemplo.

Outra profissão que gosto de citar é a do arquiteto, já que as idéias para padrões de projetos foram inspiradas por um. Só que a diferença é que nós desenvolvedores temos que arquitetar sistemas que além de possuir uma base boa e forte, todo o produto deve ser escalável e possível de adicionar novas funcionalidades, algo que para um arquiteto seria bem mais limitado.

Enquanto a tecnologia não evoluir a ponto de escrever programas inteiros usando inteligência artificial e automações, apesar de escrevermos códigos que serão lidos pela máquina, não se esqueça que eles também serão lidos por outros humanos.

Discussion (1)

Collapse
wesleysabino profile image
Wesley Sabino • Edited

Mas que conteúdo supimpa!