Aprendendo a domar o useCallback Hook do React
Publicados: 2022-04-27Não é nenhum segredo que o React.js se tornou muito popular nos últimos anos. Agora é a biblioteca JavaScript de escolha para muitos dos jogadores mais proeminentes da Internet, incluindo Facebook e WhatsApp.
Uma das principais razões para o seu surgimento foi a introdução de ganchos na versão 16.8. Os ganchos do React permitem que você acesse a funcionalidade do React sem escrever componentes de classe. Agora, componentes funcionais com ganchos se tornaram a estrutura preferida dos desenvolvedores para trabalhar com o React.
Neste post do blog, vamos nos aprofundar em um gancho específico - useCallback
- porque ele aborda uma parte fundamental da programação funcional conhecida como memoização. Você saberá exatamente como e quando utilizar o gancho useCallback
e aproveitar ao máximo seus recursos de aprimoramento de desempenho.
Preparar? Vamos mergulhar!
O que é memoização?
Memoização é quando uma função complexa armazena sua saída para que na próxima vez seja chamada com a mesma entrada. É semelhante ao cache, mas em um nível mais local. Ele pode pular quaisquer cálculos complexos e retornar a saída mais rapidamente, pois já está calculada.
Isso pode ter um efeito significativo na alocação e no desempenho da memória, e essa tensão é o que o gancho useCallback
deve aliviar.
React's useCallback vs useMemo
Neste ponto, vale a pena mencionar que useCallback
combina muito bem com outro gancho chamado useMemo
. Vamos discutir os dois, mas neste artigo, vamos nos concentrar em useCallback
como o tópico principal.
A principal diferença é que useMemo
retorna um valor memoizado, enquanto useCallback
retorna uma função memoizada. Isso significa que useMemo
é usado para armazenar um valor calculado, enquanto useCallback
retorna uma função que você pode chamar mais tarde.
Esses ganchos lhe devolverão uma versão em cache, a menos que uma de suas dependências (por exemplo, estado ou props) seja alterada.
Vamos dar uma olhada nas duas funções em ação:
import { useMemo, useCallback } from 'react' const values = [3, 9, 6, 4, 2, 1] // This will always return the same value, a sorted array. Once the values array changes then this will recompute. const memoizedValue = useMemo(() => values.sort(), [values]) // This will give me back a function that can be called later on. It will always return the same result unless the values array is modified. const memoizedFunction = useCallback(() => values.sort(), [values])
O trecho de código acima é um exemplo artificial, mas mostra a diferença entre os dois retornos de chamada:
-
memoizedValue
se tornará a matriz[1, 2, 3, 4, 6, 9]
. Enquanto a variável de valores permanecer,memoizedValue
, e nunca será recalculada. -
memoizedFunction
será uma função que retornará o array[1, 2, 3, 4, 6, 9]
.
O que é ótimo sobre esses dois retornos de chamada é que eles ficam armazenados em cache e permanecem até que a matriz de dependências seja alterada. Isso significa que em uma renderização, eles não terão lixo coletado.
Renderizando e Reagindo
Por que a memoização é importante quando se trata de React?
Tem a ver com como o React renderiza seus componentes. O React usa um DOM virtual armazenado na memória para comparar dados e decidir o que atualizar.
O DOM virtual ajuda o React com desempenho e mantém seu aplicativo rápido. Por padrão, se algum valor em seu componente for alterado, todo o componente será renderizado novamente. Isso torna o React “reativo” à entrada do usuário e permite que a tela seja atualizada sem recarregar a página.
Você não deseja renderizar seu componente porque as alterações não afetarão esse componente. É aqui que a memorização por meio useCallback
e useMemo
é útil.
Quando o React renderiza novamente seu componente, ele também recria as funções que você declarou dentro do seu componente.
Observe que ao comparar a igualdade de uma função com outra função, elas sempre serão falsas. Como uma função também é um objeto, ela só será igual a si mesma:
// these variables contain the exact same function but they are not equal const hello = () => console.log('Hello Matt') const hello2 = () => console.log('Hello Matt') hello === hello2 // false hello === hello // true
Em outras palavras, quando o React renderizar novamente seu componente, ele verá todas as funções declaradas em seu componente como sendo novas funções.
Isso é bom na maioria das vezes, e funções simples são fáceis de calcular e não afetarão o desempenho. Mas nas outras vezes em que você não quer que a função seja vista como uma nova função, você pode contar com useCallback
para ajudá-lo.
Você pode estar pensando: “Quando eu não gostaria que uma função fosse vista como uma nova função?” Bem, existem certos casos em que useCallback
faz mais sentido:
- Você está passando a função para outro componente que também é memoizado (
useMemo
) - Sua função tem um estado interno que precisa ser lembrado
- Sua função é uma dependência de outro gancho, como
useEffect
por exemplo
Benefícios de desempenho do React useCallback
Quando useCallback
é usado adequadamente, ele pode ajudar a acelerar seu aplicativo e impedir que os componentes sejam renderizados novamente se não forem necessários.
Digamos, por exemplo, que você tenha um componente que busca uma grande quantidade de dados e é responsável por exibir esses dados na forma de um gráfico ou gráfico, assim:
Suponha que o componente pai do componente da visualização de dados seja renderizado novamente, mas as props ou o estado alterados não afetem esse componente. Nesse caso, você provavelmente não deseja ou precisa renderizá-lo novamente e buscar novamente todos os dados. Evitar essa nova renderização e busca pode economizar a largura de banda do usuário e proporcionar uma experiência de usuário mais suave.
Desvantagens do React useCallback
Embora este gancho possa ajudá-lo a melhorar o desempenho, ele também vem com suas armadilhas. Algumas coisas a serem consideradas antes de usar useCallback
(e useMemo
) são:
- Coleta de lixo: As outras funções que ainda não foram memorizadas serão descartadas pelo React para liberar memória.
- Alocação de memória: Semelhante à coleta de lixo, quanto mais funções memorizadas você tiver, mais memória será necessária. Além disso, cada vez que você usa esses callbacks, há um monte de código dentro do React que precisa usar ainda mais memória para fornecer a saída em cache.
- Complexidade do código: quando você começa a agrupar funções nesses ganchos, você aumenta imediatamente a complexidade do seu código. Agora requer mais compreensão de por que esses ganchos estão sendo usados e confirmação de que eles estão sendo usados corretamente.
Estar ciente das armadilhas acima pode poupar a dor de cabeça de tropeçar nelas você mesmo. Ao considerar o uso de useCallback
, certifique-se de que os benefícios de desempenho superem as desvantagens.
Reagir use Exemplo de callback
Abaixo está uma configuração simples com um componente Button e um componente Counter. O Counter tem duas partes de estado e renderiza dois componentes Button, cada um que atualizará uma parte separada do estado dos componentes Counter.
O componente Button recebe dois adereços: handleClick
e name. Cada vez que o Button é renderizado, ele será registrado no console.
import { useCallback, useState } from 'react' const Button = ({handleClick, name}) => { console.log(`${name} rendered`) return <button onClick={handleClick}>{name}</button> } const Counter = () => { console.log('counter rendered') const [countOne, setCountOne] = useState(0) const [countTwo, setCountTwo] = useState(0) return ( <> {countOne} {countTwo} <Button handleClick={() => setCountOne(countOne + 1)} name="button1" /> <Button handleClick={() => setCountTwo(countTwo + 1)} name="button1" /> </> ) }
Neste exemplo, sempre que você clicar em um dos botões, verá isso no console:
// counter rendered // button1 rendered // button2 rendered
Agora, se aplicarmos useCallback
às nossas funções handleClick
e envolver nosso Button em React.memo
, podemos ver o que useCallback
nos fornece. React.memo
é semelhante ao useMemo
e nos permite memorizar um componente.
import { useCallback, useState } from 'react' const Button = React.memo(({handleClick, name}) => { console.log(`${name} rendered`) return <button onClick={handleClick}>{name}</button> }) const Counter = () => { console.log('counter rendered') const [countOne, setCountOne] = useState(0) const [countTwo, setCountTwo] = useState(0) const memoizedSetCountOne = useCallback(() => setCountOne(countOne + 1), [countOne) const memoizedSetCountTwo = useCallback(() => setCountTwo(countTwo + 1), [countTwo]) return ( <> {countOne} {countTwo} <Button handleClick={memoizedSetCountOne} name="button1" /> <Button handleClick={memoizedSetCountTwo} name="button1" /> </> ) }
Agora, quando clicarmos em qualquer um dos botões, veremos apenas o botão em que clicamos para fazer login no console:
// counter rendered // button1 rendered // counter rendered // button2 rendered
Aplicamos a memoização ao nosso componente de botão e os valores de prop que são passados para ele são vistos como iguais. As duas funções handleClick
são armazenadas em cache e serão vistas como a mesma função pelo React até que o valor de um item na matriz de dependências seja alterado (por exemplo, countOne
, countTwo
).
Resumo
Por mais legais que useCallback
e useMemo
sejam, lembre-se de que eles têm casos de uso específicos — você não deve envolver todas as funções com esses ganchos. Se a função for computacionalmente complexa, uma dependência de outro gancho ou um prop passado para um componente memoizado são bons indicadores que você pode querer alcançar para useCallback
.
Esperamos que este artigo tenha ajudado você a entender essa funcionalidade avançada do React e tenha ajudado a ganhar mais confiança com a programação funcional ao longo do caminho!