O Hook de Otimização de Desempenho useCallback no React
Nesta lição, veremos o seguinte
hook para otimização de desempenho:
useCallback.
O hook useCallback é semelhante à API useMemo,
a diferença é que o primeiro
armazena em cache um valor entre as renderizações
de tela, e o segundo - uma função de retorno de chamada (callback).
Isso nos permite não reiniciar funções
que consomem muitos recursos quando não é necessário e pode
ser usado ao
passar uma função
para componentes filhos.
Vamos entender melhor com um exemplo.
Primeiro, vamos criar um componente App
e definir um estado num nele:
const [num, setNum] = useState(0);
Vamos ter um botão, ao clicar
no qual num é incrementado
em 1, e um parágrafo onde
exibiremos o valor de num:
return (
<div>
<button onClick={() => setNum(num + 1)}>clique</button>
<p>cliques: {num}</p>
</div>
);
Agora, suponha que temos em
App outra lista de itens
que será complementada ao clicar em outro botão. Para armazenar
os elementos desta lista, vamos criar
um estado items:
const [items, setItems] = useState([]);
E então escreveremos uma função addItem
para adicioná-los:
function addItem() {
setItems([...items, 'novo item']);
}
Agora vamos escrever o código para exibir
os elementos da lista e movê-lo para um componente
filho Items, que receberá o array de elementos
e a função para adicioná-los como props. Vamos lembrar
de adicionar um log no console para vermos
quando nosso Items será
renderizado novamente:
function Items({ items, addItem }) {
const result = items.map((item, index) => {
return <p key={index}>{item}</p>;
});
console.log('Items render');
return (
<div>
<h3>Nossos itens</h3>
{result}
<button onClick={addItem}>adicionar item</button>
</div>
);
}
export default Items;
Vamos colocar Items no final do componente
App e passar para ele o array
items e a função para adicionar
elementos addItem:
return (
<>
<div>
<button onClick={() => setNum(num + 1)}>clique</button>
<p>cliques: {num}</p>
<br />
</div>
<Items items={items} addItem={addItem} />
</>
);
Agora vamos clicar nos botões
e verificar que num aumenta e
novos elementos são adicionados à lista.
Ao abrir o console, veremos que
nossa lista é renderizada novamente a cada
vez, mesmo se clicarmos no botão
que aumenta num.
Se temos uma lista pequena, está tudo
bem, mas e se for esperado que ela
seja grande e tenha muitos outros elementos?
Sem problemas - você dirá, pois na lição
anterior vimos a API memo,
exatamente para evitar renderizações desnecessárias
de componentes.
Então, vamos envolver nosso componente
Items em memo e pronto.
A propósito, isso pode ser feito diretamente
na exportação de Items:
export default memo(Items);
Não vamos esquecer de importar memo:
import { memo } from 'react';
Agora vamos abrir o console e clicar
nos botões. Todos os esforços em vão! Nós
memorizamos o componente, mas ao clicar
no botão 'clique' o componente
Items ainda assim
é renderizado novamente a cada vez.
O problema é que quando o componente
pai é renderizado novamente, suas funções
são recriadas - isso se aplica à nossa
função addItem, que passamos para
Items.
É exatamente nesse momento que o hook
useCallback nos ajudará. Vamos aplicá-lo.
Primeiro, vamos importá-lo para
App:
import { useCallback } from 'react';
Em seguida, vamos transformar a simples declaração de função
addItem em uma
Expressão de Função (Function Expression), especificar como
primeiro parâmetro para useCallback
nossa função na forma de um callback. Como segundo
parâmetro, entre colchetes, especificaremos
as dependências - todas as variáveis reativas
participantes da função, no nosso caso
é o array items:
const addItem = useCallback(() => {
setItems(() => [...items, 'Novo item']);
}, [items]);
Pronto! Dessa forma, armazenamos em cache
a função. Clicamos novamente nos botões e
vemos que agora, ao clicar no botão
'clique', nosso componente filho não
é mais renderizado novamente.
Crie um componente App, coloque
nele um parágrafo com texto. Crie
um estado com o valor inicial 'texto'
e exiba-o no parágrafo. Faça com que ao clicar
no parágrafo, um ponto de exclamação seja
adicionado ao final do texto.
Crie um componente filho Products,
no qual você terá um botão para adicionar
um novo produto. Coloque-o em App.
No componente pai, crie um estado
com um array de produtos e uma função de adição
de um novo produto. Passe-os como
props para o componente filho, exiba nele
o array passado como uma lista ul.
Em Products, exiba no console o texto
'products render'.
Envolva Products em memo.
Clique no parágrafo e no botão. Verifique
que ao clicar no parágrafo, o componente filho
ainda é renderizado novamente.
Armazene em cache a função para adicionar
produtos, envolvendo-a no hook useCallback.
Clique no parágrafo e no botão. Verifique
que ao clicar no parágrafo, o componente filho
não é mais renderizado novamente.