Melhorando o Desempenho do React: useCallback vs. useMemo Hooks

Melhorando o Desempenho do React: useCallback vs. useMemo Hooks
AdsTerra, Junte-se ao AdsTerra

O React é um popular framework JavaScript para o desenvolvimento front-end que facilitou a vida dos desenvolvedores ao introduzir o desenvolvimento orientado a componentes.

Antes do React 16, os componentes eram geralmente definidos com Classes e tinham diferentes métodos de ciclo de vida. O React 16 promoveu a criação de componentes funcionais e introduziu os seguintes hooks:

  • useEffect para lidar com o ciclo de vida do componente.
  • useState para gerenciamento de estado.

Esses hooks são necessários porque as atualizações e recomputações são operações custosas, então minimizá-las melhora o desempenho da aplicação.

Recentemente, o React introduziu dois hooks, useCallback e useMemo. Assim como os hooks useEffect e useState, essas são ferramentas poderosas que podem ajudar os desenvolvedores a otimizar o desempenho de suas aplicações, reduzindo atualizações e recomputações desnecessárias.

Neste artigo, exploraremos esses hooks, analisando sua funcionalidade e examinando como eles funcionam. Também discutiremos os casos de uso apropriados para esses hooks, fornecendo exemplos práticos para ajudá-lo a entender quando e onde usá-los em seus projetos. Vamos começar!

useCallback hook

Sintaxe

const computedFn = useCallback(()=>{
  doSomething(dependencies);
}, [dependencies]);

No exemplo de código anterior, fazerAlgo(dependências); será retornado e armazenado na variável funcaoCalculada. Isso só ocorre quando as dependências mudam, e o useCallback fornecerá o novo valor de fazerAlgo(dependências);.

Isso é útil quando você deseja que uma função evite a renderização novamente sempre que o componente mudar.

Por exemplo, usaremos a função de debounce no campo de entrada para obter os dados apenas quando o usuário parar de escrever por um período específico.

import React, { useState } from "https://esm.sh/react@18.2.0";
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
const App = () => {
  const [texto, setTexto] = useState("");
  const debounce = (funcao, atraso) => {
	let inDebounce;
	return function () {
  	const contexto = this;
  	const args = arguments;
  	clearTimeout(inDebounce);
  	inDebounce = setTimeout(() => funcao.apply(contexto, args), atraso);
	};
  };
  const fazerChamadaApi = () => {
	console.log(texto, " Fazendo uma chamada à API");
  };
  const chamadaApiDebounced = debounce(fazerChamadaApi, 500);
  const manipuladorDeAlteracao = (e) => {
	setTexto(e.target.value);
	chamadaApiDebounced();
  };
  return (
	<div>
  	<input type="text" onChange={manipuladorDeAlteracao} value={texto} />
	</div>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));

Cada vez que o estado é atualizado, o componente é re-renderizado, e a função de debounce é novamente anexada à variável chamadaApiDebounced. Como resultado, os logs são impressos toda vez que o estado muda, e o debounce precisa ser corrigido.

Para evitar isso, podemos envolver a função de debounce dentro do useCallback, e o programa escreverá a função de debounce memorizada que mudará apenas se as dependências mudarem.

import React, { useState, useCallback } from "https://esm.sh/react@18.2.0";
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
const App = () => {
  const [texto, setTexto] = useState("");
  const _debounce = (funcao, atraso) => {
	let inDebounce;
	return function () {
  	 const contexto = this;
  	 const args = arguments;
  	 clearTimeout(inDebounce);
  	 inDebounce = setTimeout(() => funcao.apply(contexto, args), atraso);
	};
  };
  const fazerChamadaApi = (e) => {
	console.log(e, "Fazendo uma chamada à API");
  };
  const debounce = useCallback(_debounce(fazerChamadaApi, 2000), []);
 
  const manipuladorDeAlteracao = (e) => {
	setTexto(e.target.value);
	debounce(e.target.value);
  };
  return (
	<div>
  	<input type="text" onChange={manipuladorDeAlteracao} value={texto} />
	</div>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));

Não queremos que a função _debounce seja re-renderizada, então não estamos passando nenhuma dependência. Este código imprimirá o log somente quando o usuário parar de escrever e passarem 2000 ms. Observe no código que estamos passando o valor para debounce(e.target.value) porque ele não pode recuperar o valor do estado.

Embora pareça estar funcionando bem, não está. Se você tiver o ESLint habilitado, notará que estamos recebendo o seguinte erro de linting para o hook useCallback:

error React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead react-hooks/exhaustive-deps

Isso ocorre porque as dependências da função que useCallback está recebendo como entrada não são conhecidas, então o ESLint quer que escrevamos uma função inline.

Além disso, a função _debounce é recriada em cada re-renderização, alterando assim sua referência e anulando o propósito de armazená-la em cache.

Para corrigir isso, podemos criar um hook personalizado useDebounce e usá-lo.

import { useState, useCallback, useRef } from "react";
const useDebounce = (funcao, atraso) => {
  const inDebounce = useRef();
  const debounce = useCallback(
	function () {
  	const contexto = this;
  	const args = arguments;
  	clearTimeout(inDebounce.current);
  	inDebounce.current = setTimeout(() => funcao.apply(contexto, args), atraso);
	},
	[funcao, atraso]
  );
  return debounce;
};
const App = () => {
  const [texto, setTexto] = useState("");
  const fazerChamadaApi = useCallback((e) => {
	console.log(e, "Fazendo uma chamada à API");
  }, []);
  const debounce = useDebounce(fazerChamadaApi, 2000);
  const manipuladorDeAlteracao = (e) => {
	setTexto(e.target.value);
	debounce(e.target.value);
  };
  return (
	<div>
  	<input type="text" onChange={manipuladorDeAlteracao} value={texto} />
	</div>
  );
};

useMemo hook

Sintaxe

const computedValue = useMemo(()=>{
  doSomething();
}, [dependencies]);

O useMemo funciona de forma semelhante ao useCallback, mas, em vez de retornar a função, ele retorna o valor calculado a partir da função, ou seja, a saída da função, e reexecuta a função apenas quando as dependências mudam para fornecer o novo resultado.

Por exemplo, digamos que temos um componente filho que deve ser renderizado apenas quando o texto tem um valor específico.

import React, { useState, useCallback } from "https://esm.sh/react@18.2.0";
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
const ComponenteFilho = ({ texto }) => {
  return <p>{texto}</p>;
};
const App = () => {
  const [texto, setTexto] = useState("");
  const manipuladorDeAlteracao = (e) => {
	setTexto(e.target.value);
  };
  return (
	<div>
  	<input type="text" onChange={manipuladorDeAlteracao} value={texto} />
  	<ComponenteFilho texto={texto} />
	</div>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));

Se a verificação condicional fosse colocada dentro do componente filho, isso acionaria uma nova renderização toda vez que o estado do pai fosse atualizado, independentemente de nossa intenção de limitá-lo a determinados valores.

O gancho useMemo aborda esse problema. Ao empregar esse hook, o componente filho se torna memorizado, garantindo que ele só seja renderizado quando as condições especificadas em suas dependências forem atendidas.

Neste exemplo, o componente filho será renderizado exclusivamente quando o texto corresponder ao termo "syncfusion".

import React, { useState, useMemo } from "https://esm.sh/react@18.2.0";
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
const ComponenteFilho = ({ texto }) => {
  return <p>{texto}</p>;
};
const App = () => {
  const [texto, setTexto] = useState("");
  const manipuladorDeAlteracao = (e) => {
	setTexto(e.target.value);
  };
 
  const componenteFilhoMemorizado = useMemo(() => <ComponenteFilho texto={texto} />, [texto === 'syncfusion'])
  return (
	<div>
  	<input type="text" onChange={manipuladorDeAlteracao} value={texto} />
  	{componenteFilhoMemorizado}
	</div>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));

Conclusão

Obrigado por ler. Este blog explorou os dois hooks mais poderosos do React, nomeadamente useCallback e useMemo, explicando suas funções e explorando sua aplicação ótima para evitar cálculos redundantes quando os componentes React são re-renderizados.

Sugestões de cursos

Descubra o caminho para se tornar um especialista em programação web. Aprenda HTML, CSS, JavaScript e os principais frameworks nesta jornada emocionante. Com instrutores experientes e materiais práticos, você desenvolverá habilidades práticas para criar sites impressionantes e aplicativos interativos. Impulsione sua carreira na indústria de tecnologia e abra portas para oportunidades de emprego lucrativas. Garanta sua vaga hoje mesmo e inicie sua jornada para se tornar um desenvolvedor web de sucesso.

Método Para Aprender a Programar do Absoluto ZERO com Node.js, React e React Native.

Curso de Node.js, React e React Native

As tecnologias ensinadas no curso são responsáveis por muitas vagas no mercado de trabalho.

Além da alta demanda, os salários vão de R$47.000,00 até R$197.000,00 anuais tendo empresas que possibilitam o trabalho remoto e até vagas Internacionais.

Para que você possa estar apto a preencher uma dessas vagas eu vou te apresentar o passo a passo para você se tornar um verdadeiro expert nessas tecnologias.

O curso te dará o passo a passo de como criar estruturar de um sistema do zero com Node.js, React e React Native.

Saiba mais sobre o curso de Node.js, React e React Native.

AdsTerra, Junte-se ao AdsTerra