Entendendo os hooks do ReactJS
Os hooks são uma feature do React que foi introduzida na release 16.8. Eles permitem que você use o estado, ciclo de vida e outros recursos do React sem escrever classes, que as vezes são um recurso problemático no Javascript já que não funcionam da mesma forma que em outras linguagens, além de ser uma forma de escrita mais verbosa se compararmos com as funções.
As classes confundem tanto pessoas quanto máquinas.
Segundo a própria documentação do React, evitar o uso de classes é muito interessante visto que não precisamos mais lidar com bind de funções ou nos preocupar com o uso do contexto this que no Javascript também funciona de uma forma diferente da maioria das linguagens.
Os hooks também são uma forma de diminuir o acoplamento da interface do usuário (UI) e a lógica do componente, pois com eles não é mais necessário o uso de Higher Order Functions (HOC), o que torna ambos mais reutilizáveis e intuitivas.
import React from "react";
export default class Exemplo extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
componentDidMount() {
document.title = `Você clicou ${this.state.count} vezes`;
}
componentDidUpdate() {
document.title = `Você clicou ${this.state.count} vezes`;
}
render() {
return (
<div>
<p>Você clicou {this.state.count} vezes</p>
<button
onClick={() =>
this.setState({ count: this.state.count + 1 })
}
>
Clique aqui
</button>
</div>
);
}
}
Componente utilizando classe
Este é um exemplo prático de como os hooks podem tornar o código mais simples e legível, acima vemos um componente que mostra na tela um contador de cliques, e logo abaixo a mesma lógica construída da forma mais moderna.
import React, { useEffect, useState } from "react";
export default function Exemplo() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Você clicou ${count} vezes`;
}, [count]);
return (
<div>
<p>Você clicou {count} vezes</p>
<button onClick={() => setCount(count + 1)}>Clique aqui</button>
</div>
);
}
Componente utilizando hooks
Componentes de função e de classe podem coexistir perfeitamente no ecossistema do React, então não é necessário reescrever componentes de classe, é preciso sempre avaliar o impacto na aplicação para evitar causar bugs que poderiam nunca existir. No momento não há nenhuma menção à remoção das classes do core do React, no entanto, a documentação incentiva o uso dos hooks em novos componentes.
useState
O hook useState é um dos mais simples e provavelmente o que você mais irá utilizar no seu dia a dia, essa função recebe apenas um argumento que é o valor inicial do estado.
A convenção para uso mais aceita, inclusive o que você encontrará na documentação do React é atribuindo o retorno da função a duas variáveis usando desestruturação. O retorno será um array com duas posições onde a primeira é o valor atual deste estado e a segunda posição é uma função que será responsável por atualizar esse estado.
import React, { useState } from 'react';
function Example() {
// é uma convenção no react usar o prefixo set na função que altera o estado
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Exemplo useState
useEffect
Este é o hook que veio para cobrir os métodos de ciclo de vida do componente, com exceção de getSnapshotBeforeUpdate, getDerivedStateFromError e componentDidCatch, mas já há planos de incluí-los em updates futuros.
O useEffect é uma função que recebe um callback como primeiro parâmetro e um array de dependências no segundo, este callback será executado sempre que houver uma alteração em qualquer uma das variáveis no array, e este é o comportamento padrão para todos os hooks que recebem o array de dependências como argumento.
import React, { useState, useEffect } from 'react';
function Exemplo() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('O componente foi montado');
return function cleanup() {
// ações do componentWillUnmount deverão estar aqui
};
}, []);
useEffect(() => {
console.log('A variável count sofreu uma alteração');
}, [count]);
return (
<div>
<p>Você clicou {count} vezes</p>
<button onClick={() => setCount(count + 1)}>
Clique aqui
</button>
</div>
);
}
Exemplo useEffect
Criar um efeito com o array de dependências vazio é equivalente ao componentDidMount, pois este será executado apenas uma vez quando o componente for montado. Para replicar o componentWillUnmount seu efeito poderá retornar uma função, ela será utilizada quando o componente for desmontado.
Alguns hooks podem trabalhar em conjunto, como é o caso do useCallback dentro do useState, isso é o que veremos mais a frente.
useCallback
Recebe como argumentos um callback e um array de dependências, useCallback retornará uma versão memoizada da função que só mudará se uma das entradas tiverem sido alteradas. Ela é útil quando uma função causa problemas de performance por excesso de renderizações em um useEffect por exemplo, pois o useCallback evitará que a referência da variável mude, o que garante que ele não seja executado em todas as renderizações, a menos que alguma de suas dependências tenha sido alterada.
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
Exemplo useCallback
Ao ler isso você pode pensar que daqui para frente é só utilizar o useCallback em todas as funções e nunca mais terá que lidar com loopings infinitos por causa de ume dependência no useEffect, certo? Bom, acontece que não é tão simples assim, se isso fosse benéfico provavelmente já teria sido implementado pelos engenheiros responsáveis como uma feature nativa.
Cada hook deve ser usado com cuidado analisando todos os colaterais que podem ser causados, ao criar uma função com o useCallback você estará colocando um passo a mais para verificação no ciclo de vida, o que poderá causar problemas se usado em excesso. Em alguns casos é possível declarar a sua função dentro do próprio useEffect, o que também é uma solução perfeitamente aceitável nesse contexto.
useMemo
É uma função que recebe um callback e um array de dependências como parâmetros e retornará o valor memoizado que deverá ser atribuído a uma variável.
O processo de memoização consiste em armazenar os resultados de chamadas de função caras e retornar o resultado em cache quando as mesmas entradas ocorrerem novamente.
Então, essa função só irá refazer os cálculos e alterar o valor da variável quando alguma das dependências no array receber uma atualização, esta otimização ajuda a evitar cálculos caros em cada renderização.
A função passada para useMemo será executa durante a renderização, então não faça nada lá que você normalmente não faria ao renderizar. Por exemplo, os efeitos colaterais devem ser tratados no useEffect e não no useMemo
Se nenhum array for fornecido, a função será chamada em cada renderização.
const memoizedValue = useMemo(() => returnExpensiveValue(a, b), [a, b]);
Exemplo useMemo