As Maravilhas da Composição com React HOCs
Olá, jovens gafanhotos! Vou trazer um pouco mais de conteúdo a respeito de React. Acredito que vai ajudar bastante, principalmente os iniciantes. E hoje, vou explicar um pouquinho sobre HOC (Higher-Order Component).
O que são Higher-Order Components?
Em resumo, um HOC é uma função que recebe um componente e retorna outro componente. 😕 São como HOF (Higher-Order Functions), que é uma função que recebe outra função e retorna uma função (agora piorou) hehehe.
Eu sei, pode parecer estranho, mas vai fazer sentido.
HOCs são muito comuns em libs como Redux (connect), Relay (createFragmentContainer), Mobx (observer) e outros. Além de ser possível criar o seu próprio HOC (é claro 🍭)
Obs.: HOCs não são parte do React e sim um pattern para desenvolvimento utilizando composição e herança.
A principal proposta dos HOCs é o compartilhamento de funcionalidades comuns entre componentes sem duplicação de código, basicamente.
Com isso, é possível criar um componente que possui um estado interno para controlar os seus "filhos".
Imagine uma situação onde temos uma página que recebe notícias e que possui vários componentes. Onde a comunicação entre eles é feita via props. Só que cada componente possui a sua própria renderização e comportamento, e que dependem do componente pai para apresentar o conteúdo. Mas enquanto os dados do pai não terminaram o carregamento, os filhos não podem ser apresentados. Poderia ser adicionado um loader em cada componente até finalizar o carregamento dos dados. Isso vai funcionar, mas pode ser um problema na manutenção, sem contar na duplicidade do código. Não é algo que vai ficar bom, então definitivamente não é o que queremos.
O que eu posso fazer com HOCs?
- Reúso de código, lógica e abstração;
- Controle de renderização;
- Abstração de estado e manipulação;
- Manipulação de props.
Existem dois tipos de HOCs, são eles:
Proxy
São HOCs que passam propriedades para os seus filhos. Inclusive é o tipo que utilizei para criar o exemplo deste artigo.
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
const newProps = {
user: currentLoggedInUser
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
}
Quando devo utilizar um proxy?
- Manipulação de propriedades
- Acessar a instância via referência
- Abstrair estado
- Encapsular um componente com outros elementos
Inheritance Inversion (Herança Inversa)
Este tipo de HOC estende a sua classe a partir do componente que lhe é enviado, tendo assim acesso a instância, estado, lifecycle, hooks e ao render props.
function iiHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
return super.render()
}
}
}
Quando devo utilizar uma Herança Inversa?
- Render Hijacking
- Manipulação de estado
Agora vamos criar o nosso HOC =D
Utilizei o create-react-app para facilitar a inicialização do projeto.
Não vou entrar em detalhes sobre como o create-react-app funciona e quando utilizá-lo, isso é assunto para um outro artigo. Mas a instalação e utilização dele é bem simples, como veremos abaixo.
Caso você possua npm 5.2+ é possível fazer a instalação da seguinte maneira:
npx create-react-app hoc-react-example
Se não tiver, você pode instalar conforme abaixo:
npm i -g create-react-app && create-react-app hoc-react-example
Logo após a instalação, execute o projeto:
Com NPM:
npm start
ou com YARN:
yarn start
Feito isto, uma aba do browser será aberta. http://localhost:3000/
Bom, como sou um apaixonado por cerveja, eu vou criar um HOC que "fabrica cerveja". Para isto deixei o projeto o mais simples possível, somente com alguns arquivos, e a estrutura ficou assim:
hoc-react-example
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
├── components
│ └── Cup.js
├── containers
│ ├── Ale.js
│ ├── Lager.js
│ └── Wise.js
├── App.js
├── index.css
├── index.js
├── logo.svg
└── serviceWorker.js
Onde components
são os componentes do projeto e containers
as páginas.
A página inicial que contém todos os HOCs ficou da seguinte forma:
import React, { Component } from 'react'
import Lager from './containers/Lager'
import Wise from './containers/Wise'
import Ale from './containers/Ale'
class App extends Component {
state = {
lager: false,
wise: false,
ale: false
}
componentDidMount() {
setTimeout(() => this.setState({ wise: true }), 2000);
setTimeout(() => this.setState({ lager: true }), 4000);
setTimeout(() => this.setState({ ale: true }), 1000);
}
render() {
const {
lager,
ale,
wise
} = this.state;
return (
<div className="App" style={{ display: "flex", flex: "1", justifyContent: "space-between" }}>
<Lager completed={lager} type="lager" />
<Ale completed={ale} />
<Wise completed={wise} />
</div>
);
}
}
export default App;
Para que a nossa fábrica de cervejas funcione, eu fiz a criação de 3 tipos de cervejas, Ale, Wise e Lager. Elas são os containers (linhas 2, 3 e 4 do exemplo acima).
Ignorem o css inline, fiz dessa forma porque não estou utilizando um pré-processador de css e também não quis criar uma folha de estilo para este caso, tendo em vista que é um exemplo bem rápido e o objetivo final é demonstrar a funcionalidade dos HOCs.
Para simular a criação das cervejas eu adicionei um timeout
, que ao ser finalizado altera o estado de cada cerveja para true
(linhas 14, 15, 16). Isso pode ser uma chamada de serviço, o resultado final é o mesmo.
E o estado de cada cerveja é enviado via props
(linhas 28, 29 e 30).
O conteúdo de cada cerveja ficou assim:
./src/containers/Ale.js
import React from 'react'
import withDrink from '../hoc'
const recipe = {
malte: '1kg',
water: '16l',
hops: '10g',
maturation: '1000'
}
const Ale = (props) => {
return (
<div>
<h1>Ale</h1>
{props.children}
</div>
)
}
export default withDrink(Ale, recipe)
./src/containers/Wise.js
import React from 'react'
import withDrink from '../hoc'
const recipe = {
malte: '1kg',
water: '14l',
hops: '12g',
maturation: '1500'
}
const Wise = (props) => {
return (
<div>
<h1>Wise</h1>
{props.children}
</div>
)
}
export default withDrink(Wise, recipe)
./src/containers/Lager.js
import React from 'react'
import withDrink from '../hoc'
const recipe = {
malte: '1,2kg',
water: '12l',
hops: '12g',
maturation: '1000'
}
const Larger = (props) => {
return (
<div>
<h1>Lager</h1>
{props.children}
</div>
)
}
export default withDrink(Larger, recipe)
Beleza, agora temos o nosso App com três cervejas prontas para serem produzidas.
Mas e o HOC?! 😐
Repare que na exportação de cada container existe uma função chamada withDrink
enviando duas propriedades, que é o próprio container e a receita de cada cerveja. É justamente nesse ponto que a magia da abstração começa a acontecer, como veremos abaixo, com a criação do HOC que irá se encarregar de fabricar a cerveja.
./src/hoc.js
import React from 'react'
import Cup from './components/Cup'
import Loading from './loading.gif'
const withDrink = (WrapperComponent, recipe) => {
const ComponentEnhanced = (props) => {
const isCompleted = props.completed
return (
<>
{
!isCompleted ? (
<div style={{ display: "flex", flex: "1" }}>
<h1>Receita:</h1>
<div>
<p><label>Malte:</label>{recipe.malte}</p>
<p><label>Water:</label>{recipe.water}</p>
<p><label>Hops:</label>{recipe.hops}</p>
<p><label>Maturation:</label>{recipe.maturaion}</p>
</div>
<img src={Loading} style={{ display: 'block', width: "328px", height: "278px", margin: '0 auto' }} />
</div>
) : (
<WrapperComponent {...props}>
{<Cup isCompleted={isCompleted} />}
</WrapperComponent>
)
}
</>
)
}
ComponentEnhanced.defaultProps = {
completed: false
}
return ComponentEnhanced
}
export default withDrink
Pronto, agora temos o HOC criado =D
Como ele funciona?!
Linha 5-criação de uma função que recebe um componente e a receita (aqui é apenas um objeto, pode ser qualquer parâmetro).
Linha 6-criação de uma função que tem como parâmetro props
.
Linha 9-Lembra que em App.js, eu passei como props
o parâmetro completed
?! Dentro do método withDrink
eu tenho todas as props
que são enviadas para dentro de cada container no arquivo ./src/App.js.
Linha 11-Retorno de um novo elemento.
Linha 12-Adição de um Fragmento no React (pode ser feito de duas formas, importando Fragment
do React ou simplesmente declarando igual na linha 11 e 31.
Linha 14-Fiz uma validação para saber se a cerveja está pronta ou não. Enquanto o valor for false
apresento a receita que é enviada por cada container e retorno um loading
, para indicar que a cerveja está em processo de fabricação =D.
Assim que o timeout do App.js é finalizado para cada container, e o valor de completed
é alterado para true, então o HOC retorna um novo component
com todas as propriedades que lhe foram enviadas e um componente cup
, que é a cerveja bem gelada sendo servida em um copo, para ser apreciada enquanto come alguns salgadinhos e assiste ao jogo do verdão! =D
E na linha 39 apenas retorno o HOC.
O resultado final ficou assim:
Lindo, não?! E bem simples!
O projeto completo se encontra no meu github: HOC React Example
Este exemplo é uma forma bem resumida da utilização de um HOC. O intuito é apenas demonstrar como é possível conectá-lo a outros componentes e fazer todo um reaproveitamento de código. Do jeito que foi criado eu tenho certeza que a receita de cada cerveja vai ser sempre apresentada (desde que recipe seja enviado, é claro) e o controle se ela foi concluída vai ser sempre o mesmo. Do contrário eu teria que copiar o código e enviar para cada container (Ale.js, Wise.js e Lager.js), o que dificultaria a manutenção do código e poderia gerar comportamentos diferentes em cada página.
Com a utilização de um HOC todo o controle fica bem mais fácil.
Quando utilizar?
Bom, como já mencionado, HOCs são muito úteis para criar composição de componentes, tendo um grande reaproveitamento de código.
HOCs são muito comuns para proteção de rotas, validação de formulários, controle de carregamento, gerenciamento de estado e props e muito mais. São muitos os casos em que podemos utilizar um HOC, mas faça isso se for necessário. É muito comum querermos utilizar algo em um projeto para aprendizado, mas devemos sempre levar em consideração se o sistema realmente precisa dessa implementação.
Dicas importantes
Não crie mutações do componente original, use composição.
HOCs não devem utilizar mutação, porque pode gerar vários problemas. Ao invés disso, se deve utilizar composição.
Não use HOCs dentro de um método render.
"React usa a identidade do componente para determinar se deve atualizar a subárvore existente ou descartá-la e montar uma nova. Se o componente retornado da renderização for idêntico (===) ao componente da renderização anterior, o React atualizará recursivamente a subárvore, diferenciando-a com a nova. Se eles não forem iguais, a subárvore anterior será completamente desmontada." React Docs
Outra forma de criar um HOC-Enhanced-style HOCs
Esta é uma outra forma de criar um Higher Order Componente. Ele tem como principal característica o retorno de uma função, ao invés de um componente. Assim, o componente encapsulado é recebido na função que irá retorná-lo, não sendo necessário passar o componente como parâmetro. Isso pode tornar o código mais limpo.
Bibliotecas como Relay e Redux utilizam essa abordagem.
A vantagem disso é quando temos vários HOCs fazendo a chamada entre eles. Podemos utilizar a composição do React para poder compartilhar a lógica entre todos os componentes, podendo tornar o código eficiente e reaproveitável.
Outra maneira de melhorar a utilização de HOCs é com render props.
Também recomendo que deem uma lida em decorators patterns.
Gostou do artigo? Então compartilhe e deixe suas palminhas (não menos que 50 hahaha 😚). Deixe o seu comentário logo abaixo, críticas construtivas são sempre bem vindas e vão me ajudar a trazer conteúdos cada vez melhores. Quer que eu escreva a respeito de algum tema? Então é só me deixar uma mensagem que vou falar a respeito :D Estou sempre disposto a ajudar e para mim é um prazer poder trazer um pouco de conhecimento.
Espero ter ajudado, valeu e abraço!
Referências: O component cup é a animação da cerveja sendo servida, retirei o CSS deste exemplo: https://codepen.io/mikegolus/pen/jJzRwJ
Todo o conteúdo do copo e sua animação foi criada por Mike Golus
https://reactjs.org/docs/higher-order-components.html
https://medium.com/reactbrasil/meu-primeiro-higher-order-component-a376efc654a8
https://blog.rocketseat.com.br/higher-order-components-hocs-no-react-e-react-native/
https://www.robinwieruch.de/gentle-introduction-higher-order-components/
https://blog.wgbn.com.br/react-higher-order-components-hocs-para-iniciantes-ae888120b50
https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e