Skip to content
This page has been auto-translated and may contain errors.View in English

Efeitos

Imagine um componente que precisa manter um timer contando após renderizar, como o componente Timer abaixo faz. A maior parte do que um componente faz acontece enquanto ele renderiza: ele lê props e state e retorna JSX. Manter um timer rodando é diferente. O mesmo vale para abrir uma subscription, sincronizar o título do documento, ou buscar dados de um servidor. Cada uma dessas ações alcança o mundo externo para tocar em algo que React não controla. useEffect é o hook para isso. Ele deixa um componente rodar código após renderizar, para que ele possa sincronizar a si mesmo com essa coisa externa.

Aqui está um efeito que inicia o timer uma vez e incrementa um contador a cada segundo:

jsx
import { useState, useEffect } from 'react'

function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const id = setInterval(() => setCount(c => c + 1), 1000)
    return () => clearInterval(id)
  }, [])

  return <p>{count}</p>
}

Três partes estão fazendo o trabalho aqui, e elas correspondem às três coisas que todo efeito lida: o que fazer, como limpar e quando executar.

A função que você passa para useEffect é o efeito em si. Este chama setInterval para iniciar um timer repetido que incrementa count em um a cada segundo.

O array de dependências

O segundo argumento, o [] no final, é o array de dependências. Ele controla quando o efeito é executado.

  • Um array vazio [] significa que o efeito é executado uma vez, após o componente aparecer na tela pela primeira vez. O timer acima usa isso: inicia o intervalo uma única vez e deixa ele rodando.
  • Um array com valores, como [a, b], significa que o efeito é executado após a primeira renderização e novamente sempre que um desses valores muda entre renderizações. Assim você ressincroniza quando algo do qual o efeito depende mudou.
  • Deixar o array de fora inteiramente significa que o efeito é executado após cada renderização. Isso raramente é o que você quer, e é uma fonte comum de loops descontrolados quando o efeito também atualiza state.

Então o array é sua resposta para a pergunta "quando esse código deve rodar novamente?" Liste os valores que o efeito lê e depende, e React o executa novamente quando qualquer um deles mudar.

Funções de limpeza

O efeito acima retorna uma função:

jsx
return () => clearInterval(id)

Essa função retornada é a função de limpeza. React a executa antes do efeito rodar novamente, e mais uma vez quando o componente é removido da tela. Seu trabalho é desfazer o que o efeito configurou.

Limpeza importa porque efeitos frequentemente iniciam algo que continua rodando por conta própria: um intervalo, um event listener, uma conexão aberta. Se o componente é removido e nada para esse intervalo, ele continua disparando para sempre e mantém uma referência a um componente que se foi. Faça isso algumas vezes e você tem um vazamento lento e atualizações chegando a coisas que não existem mais. A regra de ouro: se um efeito inicia algo, se inscreve em algo, ou abre algo, sua função de limpeza deve parar, desinscrever-se ou fechar isso.

Você pode não precisar de um efeito

Efeitos servem para sincronizar com o mundo externo, então muito código que parece ser candidato para useEffect pertence a outro lugar. Dois casos aparecem constantemente.

O primeiro é valores derivados. Se você pode calcular algo a partir de props e state que você já tem, calcule durante a renderização:

jsx
function Cart({ items }) {
  const total = items.reduce((sum, item) => sum + item.price, 0)
  return <p>Total: {total}</p>
}

total é derivado de items, então é computado a cada renderização e sempre está sincronizado. Armazená-lo em seu próprio state e atualizá-lo a partir de um efeito seria mais código e uma renderização extra sem ganho.

O segundo é responder a uma ação do usuário. Quando algo deve acontecer porque o usuário clicou ou digitou, esse código vai no event handler para essa ação:

jsx
function BuyButton({ product }) {
  function handleClick() {
    buyProduct(product)
  }
  return <button onClick={handleClick}>Buy</button>
}

A compra acontece em handleClick, exatamente onde o clique é tratado. Roteiá-la através de um efeito adicionaria uma camada de indireção e tornaria o fluxo mais difícil de seguir. Um bom teste: se o código é executado por causa de uma interação específica, use um event handler; se é executado para manter o componente sincronizado com um sistema externo, use um efeito.

A pergunta prática com qualquer efeito é o que pertence ao array de dependências. A resposta é mecânica: todo valor reativo que o efeito lê, ou seja, toda prop, variável de state ou valor derivado delas que aparece dentro da função efeito, pertence ao array. Deixe uma de fora e o efeito continua usando uma cópia antiga dela em vez de pegar as mudanças.

jsx
function SearchResults({ query }) {
  const [results, setResults] = useState([])

  useEffect(() => {
    let active = true
    fetch(`/api/search?q=${query}`)
      .then(r => r.json())
      .then(data => { if (active) setResults(data) })
    return () => { active = false }
  }, [query])

  return <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>
}

Este efeito lê query, então query está no array. Nada mais em escopo muda o que o efeito faz, então nada mais precisa estar lá. Você não precisa trabalhar isso de memória. A regra eslint-plugin-react-hooks exhaustive-deps lê a função efeito e marca qualquer valor que ela usa que está faltando do array. Trate seus avisos como bugs reais para corrigir, não como ruído para silenciar, pois um aviso suprimido aqui é exatamente como bugs de valor obsoleto chegam à produção.

Quando um efeito começa a fazer dois trabalhos não relacionados, divida-o em dois efeitos em vez de um efeito com uma lista de dependências mais longa. Um componente que busca dados do usuário e define o título do documento baseado naquele usuário é mais fácil de raciocinar como duas chamadas useEffect separadas, cada uma com seu próprio array de dependências focado, do que como um efeito equilibrando ambas as preocupações. Cada efeito deve sincronizar uma coisa com o mundo externo.

Um teste rápido para saber se código pertence a um efeito: ele busca algo, se inscreve em algo ou alcança e toca o DOM ou algum outro sistema fora de React diretamente? Esse é território de efeito. Ele calcula um valor a partir de props ou state que você já tem? Isso pertence ao corpo da renderização, não a um efeito. Recorrer a useEffect para derivar um valor é a forma mais comum de efeitos serem abusados.

O timing do efeito vale a pena ser preciso. Seu efeito não é executado durante a renderização. React renderiza o componente, confirma a atualização do DOM, o navegador pinta, e só então o efeito é executado. Isso é deliberado: o efeito vê uma tela que já reflete a renderização atual, e nunca bloqueia o navegador de pintar. Também significa que você não pode confiar em um efeito para produzir algo que o usuário vê antes da primeira pintura. Para o caso mais raro onde você precisa medir ou mutar o DOM antes da pintura, existe useLayoutEffect, que é executado sincronamente após o commit e antes do navegador pintar.

Você vai notar em desenvolvimento que efeitos são executados duas vezes no mount. O Strict Mode de React intencionalmente monta cada componente, desmonta ele e monta novamente. Ele executa seu efeito, executa a limpeza, então executa o efeito uma segunda vez. Isso é um teste de estresse para a limpeza. Se seu efeito configura algo mas nunca o desconstrói, a execução dupla expõe o bug imediatamente, porque você verá dois intervalos ou duas subscriptions em vez de um. Isso só acontece em desenvolvimento. Em produção o efeito é executado uma vez.

Duas armadilhas do array de dependências valem a pena internalizar. A primeira é closures obsoletas. Um efeito captura os valores da renderização em que foi criado. Se você deixa um valor de fora do array de dependências, o efeito continua lendo o valor antigo daquela renderização mesmo após ele ter mudado, e nunca é executado novamente para pegar o novo. A correção é listar todo valor que o efeito lê, ou usar a forma de updater como setCount(c => c + 1) para você não precisar ler o valor atual. A segunda é identidade. Objetos, arrays e funções criados durante a renderização são valores novos toda vez, então listar um como dependência faz o efeito ser executado novamente a cada renderização, pois a referência nunca é igual à da última vez. Quando você precisa de um como dependência, defina-o dentro do efeito, ou envolva-o em useMemo ou useCallback (hooks que cacheiam um valor ou função entre renderizações) para que sua identidade permaneça estável entre renderizações.

Version note

In class components this behavior was split across three lifecycle methods: componentDidMount for setup after the first render, componentDidUpdate for re-running when data changed, and componentWillUnmount for cleanup. A single useEffect with a dependency array and a cleanup function covers all three, which is why related setup and teardown code now lives together instead of being spread across separate methods.

JunoEfeitos sincronizam com o mundo externo Pense em useEffect como o lugar para código que alcança fora de React: um timer, o título do documento, uma requisição a um servidor.

O array no final diz quando executar, [] significando uma vez, logo após o componente aparecer. E se seu efeito inicia algo, retorne uma pequena função que o para.

A maior parte das vezes você não vai precisar de um efeito, então é um bom hábito pausar e perguntar se um cálculo simples ou um click handler não faria o trabalho primeiro.

JunoEfeitos sincronizam com o mundo externouseEffect é executado após renderizar para sincronizar com algo que React não controla.

O array de dependências é o controle: [] executa uma vez, [a, b] executa novamente quando aqueles mudam, e sem array executa a cada renderização. Retorne uma função de limpeza para qualquer coisa que você inicia, se inscreve ou abre.

Antes de escrever um, verifique se o valor pode ser derivado durante a renderização ou o trabalho pertence a um event handler, porque aqueles cobrem uma quantidade surpreendente do que parece ser território de efeito.

JunoEfeitos sincronizam com o mundo externo Efeitos são executados após commit, então eles veem uma tela pintada e nunca a bloqueiam; recorra a useLayoutEffect apenas quando você absolutamente precisa tocar o DOM antes da pintura.

O duplo mount do Strict Mode em desenvolvimento é um teste de limpeza, então trate qualquer intervalo ou subscription duplicado como um bug real.

As duas armadilhas de dependência são closures obsoletas de valores omitidos e identidades instáveis de objeto ou função que executam o efeito a cada renderização; a forma de updater, useMemo e useCallback são como você as neutraliza.

Próximo: Buscando dados, onde componentes trazem dados que vivem completamente fora de React.