High Order Components com React para iniciantes
Como dar novos poderes as suas funções e componentes utilizando composição e o conceito de High Order Functions / High Order Components.
Prefácio
Estou escrevendo isso porque todos os outros artigos — incluindo a documentação oficial do React sobre componentes de ordem superior — é um inferno para compreender enquanto iniciante. Compreendi que os Componentes de Ordem Superior (High Order Components) eram uma coisa, mas eu realmente não entendia como eram úteis. Este artigo tem como objetivo esclarecer sobre esse tipo de componente as famosas HOCs.
Antes de poder entender HOCs, primeiro devemos entender algumas coisas sobre funções em JavaScript.
Breve introdução sobre arrow functions em ES6
Este artigo fornecerá exemplos exclusivamente usando as arrow functions do ES6. Se você nunca viu uma arrow function antes, elas são essencialmente equivalentes a função regular. O código abaixo mostra as diferenças entre funções regulares e arrow functions.
function () {
return 42
}
// same as:
() => 42
// same as:
() => {
return 42
}
function person(name) {
return { name: name }
}
// same as:
(name) => {
return { name: name }
}
Leia a documentação de arrow functions no MDN para uma compreensão mais completa.
Funções como Valores e Aplicação Parcial
Assim como números, strings, booleans, etc., as funções são valores . Isso significa que você pode passar funções ao redor como qualquer outro dado. Você pode passar uma função como um argumento para outra função:
const execute = (someFunction) => someFunction()execute(() => alert('Executed'))
E você pode retornar uma função de uma função.
const getOne = () => () => 1getOne()()
A razão pela qual temos dois ()
depois dogetOne
é que a primeira aplicação da função retorna outra. Ilustração:
const getOne = () => () => 1getOne
//=> () => () => 1getOne()
//=> () => 1getOne()()
//=> 1
A coisa útil sobre funções que retorna funções é que podemos escrever funções que acompanham a entrada inicial. Por exemplo, a função abaixo aceita um número como argumento e retorna uma função que multiplica esse argumento por um novo:
const multiply = (x) => (y) => x * ymultiply(5)(20)
Este exemplo funciona da mesma forma que getOne
, cada parêntese aplica alguma entrada para a função. Neste caso, estamos atribuindo x
a 5
e y
para 20
.
const multiply = (x) => (y) => x * ymultiply
//=> (x) => (y) => x * ymultiply(5)
//=> (y) => 5 * ymultiply(5)(20)
//=> 5 * 20
Quando chamamos a função multiply
com apenas um argumento, estamos parcialmente aplicando a função. Quando chamamos multiply(5)
, obtemos uma função que irá multiplicar a entrada em 5. Se chamarmos multiply(7)
, obteremos uma função que irá multiplicar a entrada por 7 e assim por diante. Podemos usar uma aplicação parcial para criar novas funções com uma entrada predefinida:
const multiply = (x) => (y) => x * yconst multiplyByFive = multiply(5)
const multiplyBy100 = multiply(100)multiplyByFive(20)
//=> 100
multiply(5)(20)
//=> 100multiplyBy100(5)
//=> 500
multiply(100)(5)
//=> 500
Isso pode não parecer super útil no início. No entanto, você pode usar uma aplicação parcial para escrever um código que seja mais fácil de ler e raciocinar. Por exemplo, podemos substituir styled-components
sintaxe de interpolação de função complexa com algo um pouco mais limpo.
// before
const Button = styled.button`
background-color: ${({ theme }) => theme.bgColor}
color: ${({ theme }) => theme.textColor}
`<Button theme={themes.primary}>Submit</Button>// after
const fromTheme = (prop) => ({ theme }) => theme[prop]const Button = styled.button`
background-color: ${fromTheme("bgColor")}
color: ${fromTheme("textColor")}
`<Button theme={themes.primary}>Submit</Button>
Criamos uma função que aceita uma seqüência de caracteres como um parâmetro: fromTheme("textColor")
que retorna uma função aceitando um objeto com uma propriedade theme
:({ theme }) => theme[prop]
a qual tentamos pesquisar através da cadeia inicial que passamos "textColor"
. Podemos ir além e escrever funções como backgroundColor
e textColor
que parcialmente aplicam a função fromTheme
:
const fromTheme = (prop) => ({ theme }) => theme[prop]
const backgroundColor = fromTheme("bgColor")
const textColor = fromTheme("textColor")const Button = styled.button`
background-color: ${backgroundColor}
color: ${textColor}
`
Funções de ordem superior
As funções de ordem superior são definidas como funções que aceitam uma função como um argumento. Você já pode estar familiarizado com funções de ordem superior se você já usou uma função como map
. Se você não está familiarizado map
, é uma função que se desloca e aplica uma função a cada elemento em uma matriz. Por exemplo, você pode colocar uma matriz de números como assim:
const square = (x) => x * x[1, 2, 3].map(square)
//=> [ 1, 4, 9 ]
Podemos escrever nossa própria versão map
para ilustrar este conceito:
const map = (fn, array) => {
const mappedArray = []for (let i = 0; i < array.length; i++) {
mappedArray.push(
// apply fn with the current element of the array
fn(array[i])
)
}return mappedArray
}
Podemos usar o nosso map
para fazer algo como quadrado uma série de números:
const square = (x) => x * xconsole.log(map(square, [1, 2, 3, 4, 5]))
//=> [ 1, 4, 9, 16, 25 ]
Ou devolva uma matriz de <li>
React Elements:
const HeroList = ({ heroes }) => (
<ul>
{map((hero) => (
<li key={hero}>{hero}</li>
), heroes)}
</ul>
)<HeroList heroes=[
"Wonder Woman",
"Black Widow",
"Spider Man",
"Storm",
"Deadpool"
]/>
/*=> (
<ul>
<li>Wonder Woman</li>
<li>Black Widow</li>
<li>Spider Man</li>
<li>Storm</li>
<li>Deadpool</li>
</ul>
)*/
Componentes de ordem superior
Sabemos que uma função de ordem superior é uma função que aceita uma função como argumento. Em React, qualquer função que retorna JSX
é conhecida como Componente Funcional sem Estado. Um Componente Funcional básico parece assim:
const Title = (props) => <h1>{props.children}</h1><Title>Higher-Order Components(HOCs) for React Newbies</Title>
//=> <h1>Higher-Order Components(HOCs) for React Newbies</h1>
Um componente de ordem superior é uma função que aceita um Componente como um argumento e retorna um novo Componente. Como você usa o componente passado depende de você. Você pode ignorá-lo completamente:
// Technically an HOC
const ignore = (anything) => (props) => <h1>:)</h1>const IgnoreHeroList = ignore(HeroList)
<IgnoreHeroList />
//=> <h1>:)</h1>
Você pode escrever um HOC que transforma sua entrada em maiúscula:
const yell = (PassedComponent) =>
({ children, ...props }) =>
<PassedComponent {...props}>
{children.toUpperCase()}!
</PassedComponent>const Title = (props) => <h1>{props.children}</h1>
const AngryTitle = yell(Title)<AngryTitle>Whatever</AngryTitle>
//=> <h1>WHATEVER!</h1>
Você também pode retornar um Componente Stateful, porque as classes em Javascript são syntax sugar para funções. Isso permite que você se encaixe nos métodos do lifecycle do React como componentDidMount
. É aí que os HOCs se tornam realmente úteis. Agora podemos fazer coisas como passar o resultado de uma solicitação HTTP como adereços para um Componente Funcional.
const withGists = (PassedComponent) =>
class WithGists extends React.Component {
state = {
gists: []
}componentDidMount() {
fetch("https://api.github.com/gists/public")
.then((r) => r.json())
.then((gists) => this.setState({
gists: gists
}))
}render() {
return (
<PassedComponent
{...this.props}
gists={this.state.gists}
/>
)
}
}const Gists = ({ gists }) => (
<pre>{JSON.stringify(gists, null, 2)}</pre>
)const GistsList = withGists(Gists)<GistsList />
//=> Before api request finishes:
// <Gists gists={[]} />
//
//=> After api request finishes:
// <Gists gists={[
// { /* … */ },
// { /* … */ },
// { /* … */ }
// ]} />
Você pode ligar withGists
para qualquer componente e irá passar para baixo o resultado da chamada api gists. Você pode ver um exemplo mais detalhado disso aqui .
Conclusão: HOCs são 🔥 🔥 🔥
Redux usa um HOC, connect
para passar valores da sua loja de aplicativos para componentes "conectados". Também faz algumas verificações de erros e otimizações do ciclo de vida do componente que, se feito manualmente, faria com que você escrevesse uma tonelada de código de referência.
Se você se achar escrevendo muitos códigos em diferentes lugares que faz o mesmo, você poderá refatorar esse código em um HOC reutilizável.
Os HOCs são realmente expressivos e você pode construir muitas coisas legais com eles. No entanto, porque eles são tão expressivos, você pode ir ao mar com eles se quiser.
Tente manter seus HOCs simples e apontar para escrever um código que não exija que você leia um artigo longo para entendê-lo.
Exercícios adicionais
Aqui estão alguns exercícios para solidificar sua compreensão de HOCs:
- Escreva um HOC que reverte sua entrada
- Escreva um HOC que fornece dados de uma API para o Componente Passado
- Escreva um HOC que implemente
shouldComponentUpdate
para evitar a reconciliação . - Escreva um HOC que use
React.Children.toArray
para ordenar as crianças passadas para o componente Passed.
⭐️ Créditos
- Higher-Order Components (HOCs) for Beginners, escrito originalmente por Brandon Newton no blog do HackerNoon
Tem interesse em trabalhar conosco? Nós estamos sempre procurando por pessoas apaixonadas por tecnologia para fazer parte da nossa tripulação! Você pode conferir nossas vagas aqui.