v19.2Latest

Sincronizando com Efeitos

Alguns componentes precisam sincronizar com sistemas externos. Por exemplo, você pode querer controlar um componente não-React com base no estado do React, configurar uma conexão com o servidor ou enviar um log de análise quando um componente aparece na tela.Efeitospermitem que você execute algum código após a renderização para que possa sincronizar seu componente com algum sistema fora do React.

Você aprenderá
  • O que são Efeitos
  • Como os Efeitos são diferentes de eventos
  • Como declarar um Efeito em seu componente
  • Como evitar reexecutar um Efeito desnecessariamente
  • Por que os Efeitos executam duas vezes em desenvolvimento e como corrigi-los

O que são Efeitos e como eles são diferentes de eventos?

Antes de chegar aos Efeitos, você precisa estar familiarizado com dois tipos de lógica dentro dos componentes React:

  • Código de renderização(introduzido emDescrevendo a UI) vive no nível superior do seu componente. É aqui que você pega as props e o estado, os transforma e retorna o JSX que deseja ver na tela.O código de renderização deve ser puro.Como uma fórmula matemática, ele deve apenascalcularo resultado, mas não fazer mais nada.
  • Manipuladores de eventos(introduzidos emAdicionando Interatividade) são funções aninhadas dentro de seus componentes quefazemcoisas em vez de apenas calculá-las. Um manipulador de eventos pode atualizar um campo de entrada, enviar uma requisição HTTP POST para comprar um produto ou navegar o usuário para outra tela. Manipuladores de eventos contêm"efeitos colaterais"(eles alteram o estado do programa) causados por uma ação específica do usuário (por exemplo, um clique em um botão ou digitação).

Às vezes isso não é suficiente. Considere um componenteChatRoomque deve se conectar ao servidor de chat sempre que estiver visível na tela. Conectar-se a um servidor não é um cálculo puro (é um efeito colateral), então não pode acontecer durante a renderização. No entanto, não há um evento único específico, como um clique, que faça com que oChatRoomseja exibido.

Efeitospermitem que você especifique efeitos colaterais causados pela própria renderização, e não por um evento específico.Enviar uma mensagem no chat é umeventoporque é causado diretamente pelo usuário clicando em um botão específico. No entanto, configurar uma conexão com o servidor é umEfeitoporque deve acontecer independentemente de qual interação fez o componente aparecer. Os Efeitos são executados no final de umcommitapós a atualização da tela. Este é um bom momento para sincronizar os componentes React com algum sistema externo (como rede ou uma biblioteca de terceiros).

Observação

Aqui e posteriormente neste texto, "Efeito" com inicial maiúscula refere-se à definição específica do React acima, ou seja, um efeito colateral causado pela renderização. Para nos referirmos ao conceito de programação mais amplo, diremos "efeito colateral".

Você pode não precisar de um Efeito

Não se apresse para adicionar Efeitos aos seus componentes.Lembre-se de que os Efeitos são normalmente usados para "sair" do seu código React e sincronizar com algum sistemaexterno. Isso inclui APIs do navegador, widgets de terceiros, rede e assim por diante. Se o seu Efeito apenas ajusta algum estado com base em outro estado,você pode não precisar de um Efeito.

Como escrever um Efeito

Para escrever um Efeito, siga estes três passos:

  1. Declare um Efeito.Por padrão, seu Efeito será executado após cadacommit.
  2. Especifique as dependências do Efeito.A maioria dos Efeitos deve ser reexecutada apenasquando necessário, e não após cada renderização. Por exemplo, uma animação de fade-in deve ser acionada apenas quando um componente aparece. Conectar e desconectar de uma sala de chat deve acontecer apenas quando o componente aparece e desaparece, ou quando a sala de chat muda. Você aprenderá a controlar isso especificandodependências.
  3. Adicione limpeza se necessário.Alguns Efeitos precisam especificar como parar, desfazer ou limpar o que estavam fazendo. Por exemplo, "conectar" precisa de "desconectar", "inscrever" precisa de "cancelar inscrição" e "buscar" precisa de "cancelar" ou "ignorar". Você aprenderá a fazer isso retornando umafunção de limpeza.

Vamos examinar cada um desses passos em detalhes.

Passo 1: Declare um Efeito

Para declarar um Efeito em seu componente, importe oHook useEffectdo React:

Em seguida, chame-o no nível superior do seu componente e coloque algum código dentro do seu Efeito:

Cada vez que seu componente renderiza, o React atualizará a telae entãoexecutará o código dentro deuseEffect. Em outras palavras,useEffect“atrasa” a execução de um trecho de código até que aquela renderização seja refletida na tela.

Vamos ver como você pode usar um Efeito para sincronizar com um sistema externo. Considere um componente React<VideoPlayer>. Seria bom controlar se ele está tocando ou pausado passando uma propisPlayingpara ele:

Seu componente personalizadoVideoPlayerrenderiza a tag<video>integrada do navegador:

No entanto, a tag<video>do navegador não possui uma propisPlaying. A única maneira de controlá-la é chamar manualmente os métodosplay() e pause()no elemento DOM.Você precisa sincronizar o valor da propisPlaying, que indica se o vídeodeveriaestar tocando no momento, com chamadas comoplay() e pause().

Primeiro, precisaremosobter uma refpara o nó DOM<video>.

Você pode ficar tentado a tentar chamarplay()oupause()durante a renderização, mas isso não está correto:

A razão pela qual este código não está correto é que ele tenta fazer algo com o nó DOM durante a renderização. No React,a renderização deve ser um cálculo purode JSX e não deve conter efeitos colaterais como modificar o DOM.

Além disso, quandoVideoPlayeré chamado pela primeira vez, seu DOM ainda não existe! Não há um nó DOM ainda para chamarplay()oupause(), porque o React não sabe qual DOM criar até que você retorne o JSX.

A solução aqui éenvolver o efeito colateral comuseEffectpara movê-lo para fora do cálculo de renderização:

Ao envolver a atualização do DOM em um Efeito, você permite que o React atualize a tela primeiro. Então seu Efeito é executado.

Quando seu componenteVideoPlayerrenderiza (seja pela primeira vez ou se ele renderizar novamente), algumas coisas acontecerão. Primeiro, o React atualizará a tela, garantindo que a tag<video>esteja no DOM com as props corretas. Então o React executará seu Efeito. Finalmente, seu Efeito chamaráplay()oupause()dependendo do valor deisPlaying.

Pressione Play/Pause várias vezes e veja como o reprodutor de vídeo permanece sincronizado com o valor deisPlaying:

Neste exemplo, o “sistema externo” que você sincronizou com o estado do React foi a API de mídia do navegador. Você pode usar uma abordagem similar para encapsular código legado não-React (como plugins jQuery) em componentes React declarativos.

Observe que controlar um reprodutor de vídeo é muito mais complexo na prática. Chamarplay()pode falhar, o usuário pode reproduzir ou pausar usando os controles internos do navegador, e assim por diante. Este exemplo é muito simplificado e incompleto.

Armadilha

Por padrão, os Efeitos são executados apóscadarenderização. É por isso que um código como estecriará um loop infinito:

Os Efeitos são executados comoresultadode uma renderização. Definir estadodisparauma renderização. Definir estado imediatamente em um Efeito é como conectar uma tomada elétrica nela mesma. O Efeito é executado, define o estado, o que causa uma re-renderização, o que faz o Efeito executar novamente, ele define o estado de novo, isso causa outra re-renderização, e assim por diante.

Os Efeitos geralmente devem sincronizar seus componentes com um sistemaexterno. Se não houver um sistema externo e você só quiser ajustar algum estado com base em outro estado,você pode não precisar de um Efeito.

Passo 2: Especificar as dependências do Efeito

Por padrão, os Efeitos são executados apóscadarenderização. Frequentemente, issonão é o que você quer:

  • Às vezes, é lento. Sincronizar com um sistema externo nem sempre é instantâneo, então você pode querer pular essa ação a menos que seja necessária. Por exemplo, você não quer reconectar ao servidor de chat a cada pressionamento de tecla.
  • Às vezes, está errado. Por exemplo, você não quer disparar uma animação de fade-in do componente a cada pressionamento de tecla. A animação deve ser reproduzida apenas uma vez, quando o componente aparece pela primeira vez.

Para demonstrar o problema, aqui está o exemplo anterior com algumas chamadasconsole.loge uma entrada de texto que atualiza o estado do componente pai. Observe como digitar faz o Efeito ser reexecutado:

Você pode dizer ao React parapular a reexecução desnecessária do Efeitoespecificando um array dedependênciascomo segundo argumento da chamadauseEffect. Comece adicionando um array vazio[]ao exemplo acima na linha 14:

Você deve ver um erro dizendoReact Hook useEffect has a missing dependency: 'isPlaying':

O problema é que o código dentro do seu Efeitodependeda propisPlayingpara decidir o que fazer, mas essa dependência não foi declarada explicitamente. Para corrigir esse problema, adicioneisPlayingao array de dependências:

Agora todas as dependências estão declaradas, então não há erro. Especificar[isPlaying]como array de dependências diz ao React que ele deve pular a reexecução do seu Efeito seisPlayingfor o mesmo que era durante a renderização anterior. Com essa alteração, digitar na entrada não faz o Efeito ser reexecutado, mas pressionar Play/Pause faz:

O array de dependências pode conter múltiplas dependências. O React só irá pular a reexecução do Effect setodasas dependências que você especificar tiverem exatamente os mesmos valores que tinham durante a renderização anterior. O React compara os valores das dependências usando a comparaçãoObject.is. Veja areferência do useEffectpara detalhes.

Observe que você não pode “escolher” suas dependências.Você receberá um erro de lint se as dependências que você especificou não corresponderem ao que o React espera com base no código dentro do seu Effect. Isso ajuda a capturar muitos bugs no seu código. Se você não quiser que algum código seja reexecutado,edite o código do Effect em si para não “precisar” dessa dependência.

Armadilha

Os comportamentos sem o array de dependências e com um array de dependênciasvazio[]são diferentes:

Vamos analisar mais de perto o que “mount” significa na próxima etapa.

Deep Dive
Por que a ref foi omitida do array de dependências?

Etapa 3: Adicione limpeza se necessário

Considere um exemplo diferente. Você está escrevendo um componenteChatRoomque precisa se conectar ao servidor de chat quando aparece. Você recebe uma APIcreateConnection()que retorna um objeto com os métodosconnect() e disconnect(). Como você mantém o componente conectado enquanto ele é exibido ao usuário?

Comece escrevendo a lógica do Efeito:

Seria lento conectar ao chat após cada nova renderização, então você adiciona o array de dependências:

O código dentro do Effect não usa nenhuma prop ou estado, então seu array de dependências é[](vazio). Isso diz ao React para executar este código apenas quando o componente “monta”, ou seja, aparece na tela pela primeira vez.

Vamos tentar executar este código:

Este Effect só executa na montagem, então você poderia esperar que"✅ Connecting..."fosse impresso uma vez no console.No entanto, se você verificar o console,"✅ Connecting..."é impresso duas vezes. Por que isso acontece?

Imagine que o componenteChatRoomé parte de um aplicativo maior com muitas telas diferentes. O usuário começa sua jornada na páginaChatRoom. O componente é montado e chamaconnection.connect(). Em seguida, imagine que o usuário navega para outra tela — por exemplo, para a página de Configurações. O componenteChatRoomé desmontado. Finalmente, o usuário clica em Voltar e oChatRoomé montado novamente. Isso estabeleceria uma segunda conexão — mas a primeira conexão nunca foi destruída! Conforme o usuário navega pelo aplicativo, as conexões continuariam se acumulando.

Bugs como esse são fáceis de passar despercebidos sem testes manuais extensivos. Para ajudá-lo a detectá-los rapidamente, no desenvolvimento o React remonta cada componente uma vez imediatamente após sua montagem inicial.

Ver o log"✅ Connecting..."duas vezes ajuda você a perceber o problema real: seu código não fecha a conexão quando o componente é desmontado.

Para corrigir o problema, retorne umafunção de limpezado seu Effect:

O React chamará sua função de limpeza cada vez antes do Effect ser executado novamente, e uma última vez quando o componente for desmontado (removido). Vamos ver o que acontece quando a função de limpeza é implementada:

Agora você obtém três logs no console em desenvolvimento:

  1. "✅ Connecting..."
  2. "❌ Disconnected."
  3. "✅ Connecting..."

Este é o comportamento correto em desenvolvimento.Ao remontar seu componente, o React verifica que navegar para longe e voltar não quebraria seu código. Desconectar e depois conectar novamente é exatamente o que deveria acontecer! Quando você implementa a limpeza corretamente, não deve haver diferença visível para o usuário entre executar o Effect uma vez versus executá-lo, limpá-lo e executá-lo novamente. Há um par extra de chamadas de conexão/desconexão porque o React está sondando seu código em busca de bugs no desenvolvimento. Isso é normal — não tente fazer isso desaparecer!

Na produção, você veria apenas"✅ Connecting..."impresso uma vez.A remontagem de componentes só acontece no desenvolvimento para ajudá-lo a encontrar Effects que precisam de limpeza. Você pode desativar oStrict Modepara optar por não usar o comportamento de desenvolvimento, mas recomendamos mantê-lo ativado. Isso permite que você encontre muitos bugs como o acima.

Como lidar com o Effect sendo disparado duas vezes no desenvolvimento?

O React intencionalmente remonta seus componentes no desenvolvimento para encontrar bugs como no último exemplo.A pergunta certa não é “como executar um Effect uma vez”, mas “como corrigir meu Effect para que funcione após a remontagem”.

Normalmente, a resposta é implementar a função de limpeza. A função de limpeza deve parar ou desfazer o que quer que o Effect estivesse fazendo. A regra geral é que o usuário não deve ser capaz de distinguir entre o Effect sendo executado uma vez (como na produção) e uma sequência deconfiguração → limpeza → configuração(como você veria no desenvolvimento).

A maioria dos Effects que você escreverá se encaixará em um dos padrões comuns abaixo.

Armadilha

Não use refs para impedir que Effects sejam disparados

Uma armadilha comum para impedir que Effects sejam disparados duas vezes no desenvolvimento é usar umarefpara evitar que o Effect seja executado mais de uma vez. Por exemplo, você poderia “corrigir” o bug acima com umuseRef:

Isso faz com que você veja"✅ Connecting..."apenas uma vez no desenvolvimento, mas não corrige o bug.

Quando o usuário navega para longe, a conexão ainda não é fechada e, quando ele navega de volta, uma nova conexão é criada. Conforme o usuário navega pelo aplicativo, as conexões continuariam se acumulando, da mesma forma que acontecia antes da “correção”.

Para corrigir o bug, não basta apenas fazer o Effect ser executado uma vez. O effect precisa funcionar após a remontagem, o que significa que a conexão precisa ser limpa como na solução acima.

Veja os exemplos abaixo para saber como lidar com padrões comuns.

Controlando widgets não-React

Às vezes você precisa adicionar widgets de UI que não são escritos em React. Por exemplo, digamos que você

Observe que não há necessidade de limpeza neste caso. Em desenvolvimento, o React chamará o Effect duas vezes, mas isso não é um problema porque chamarsetZoomLevelduas vezes com o mesmo valor não faz nada. Pode ser um pouco mais lento, mas isso não importa porque não será desmontado desnecessariamente em produção.

Algumas APIs podem não permitir que você as chame duas vezes seguidas. Por exemplo, o métodoshowModaldo elemento integrado<dialog>lança um erro se você o chamar duas vezes. Implemente a função de limpeza e faça com que ela feche o diálogo:

Em desenvolvimento, seu Effect chamaráshowModal(), depois imediatamenteclose(), e entãoshowModal()novamente. Isso tem o mesmo comportamento visível para o usuário que chamarshowModal()uma vez, como você veria em produção.

Inscrevendo-se em eventos

Se seu Effect se inscrever em algo, a função de limpeza deve cancelar a inscrição:

Em desenvolvimento, seu Effect chamaráaddEventListener(), depois imediatamenteremoveEventListener(), e entãoaddEventListener()novamente com o mesmo manipulador. Portanto, haveria apenas uma inscrição ativa por vez. Isso tem o mesmo comportamento visível para o usuário que chamaraddEventListener()uma vez, como em produção.

Disparando animações

Se seu Effect animar algo, a função de limpeza deve redefinir a animação para os valores iniciais:

Em desenvolvimento, a opacidade será definida como1, depois como0, e então como1novamente. Isso deve ter o mesmo comportamento visível para o usuário que defini-la como1diretamente, que é o que aconteceria em produção. Se você usar uma biblioteca de animação de terceiros com suporte a interpolação, sua função de limpeza deve redefinir a linha do tempo para seu estado inicial.

Buscando dados

Se seu Effect buscar algo, a função de limpeza deveabortar a buscaou ignorar seu resultado:

Você não pode “desfazer” uma requisição de rede que já aconteceu, mas sua função de limpeza deve garantir que a busca quenão é mais relevantenão continue afetando sua aplicação. Se ouserIdmudar de'Alice'para'Bob', a limpeza garante que a resposta de'Alice'seja ignorada mesmo que chegue depois de'Bob'.

Em desenvolvimento, você verá duas buscas na aba Network.Não há nada de errado com isso. Com a abordagem acima, o primeiro Effect será limpo imediatamente, então sua cópia da variávelignoreserá definida comotrue. Portanto, mesmo que haja uma requisição extra, ela não afetará o estado graças à verificaçãoif (!ignore).

Em produção, haverá apenas uma requisição.Se a segunda requisição em desenvolvimento estiver incomodando você, a melhor abordagem é usar uma solução que deduplica requisições e armazena em cache suas respostas entre componentes:

Isso não apenas melhorará a experiência de desenvolvimento, mas também fará sua aplicação parecer mais rápida. Por exemplo, o usuário pressionando o botão Voltar não terá que esperar alguns dados carregarem novamente porque eles estarão em cache. Você pode construir esse cache por conta própria ou usar uma das muitas alternativas à busca manual em Effects.

Deep Dive
Quais são boas alternativas para busca de dados em Effects?

Enviando análises

Considere este código que envia um evento de análise na visita à página:

Em desenvolvimento,logVisitserá chamada duas vezes para cada URL, então você pode ficar tentado a tentar corrigir isso.Recomendamos manter este código como está.Como nos exemplos anteriores, não há diferença de comportamentovisível para o usuárioentre executá-la uma vez ou duas vezes. Do ponto de vista prático,logVisitnão deve fazer nada em desenvolvimento, pois você não quer que os logs das máquinas de desenvolvimento distorçam as métricas de produção. Seu componente é remontado toda vez que você salva seu arquivo, então ele registra visitas extras em desenvolvimento de qualquer maneira.

Na produção, não haverá logs de visitas duplicados.

Para depurar os eventos de análise que você está enviando, você pode implantar seu aplicativo em um ambiente de staging (que é executado no modo de produção) ou optar temporariamente por sair doStrict Modee suas verificações de remontagem apenas para desenvolvimento. Você também pode enviar análises dos manipuladores de eventos de mudança de rota em vez de usar Effects. Para análises mais precisas,observadores de interseçãopodem ajudar a rastrear quais componentes estão na viewport e por quanto tempo permanecem visíveis.

Não é um Effect: Inicializando a aplicação

Alguma lógica deve ser executada apenas uma vez quando a aplicação inicia. Você pode colocá-la fora de seus componentes:

Isso garante que tal lógica seja executada apenas uma vez após o navegador carregar a página.

Não é um Effect: Comprando um produto

Às vezes, mesmo que você escreva uma função de limpeza, não há como evitar consequências visíveis para o usuário de executar o Effect duas vezes. Por exemplo, talvez seu Effect envie uma requisição POST como comprar um produto:

Você não gostaria de comprar o produto duas vezes. No entanto, essa também é a razão pela qual você não deve colocar essa lógica em um Effect. E se o usuário for para outra página e depois pressionar Voltar? Seu Effect seria executado novamente. Você não quer comprar o produto quando o usuáriovisitauma página; você quer comprá-lo quando o usuárioclicano botão Comprar.

Comprar não é causado pela renderização; é causado por uma interação específica. Deve ser executado apenas quando o usuário pressiona o botão.Exclua o Effect e mova sua requisição/api/buypara o manipulador de eventos do botão Comprar:

Isso ilustra que, se a remontagem quebrar a lógica da sua aplicação, isso geralmente revela bugs existentes.Da perspectiva do usuário, visitar uma página não deve ser diferente de visitá-la, clicar em um link e depois pressionar Voltar para visualizar a página novamente. O React verifica que seus componentes respeitam esse princípio remontando-os uma vez no desenvolvimento.

Juntando tudo

Este playground pode ajudá-lo a "sentir" como os Efeitos funcionam na prática.

Este exemplo usasetTimeoutpara agendar um log no console com o texto de entrada para aparecer três segundos após a execução do Efeito. A função de limpeza cancela o timeout pendente. Comece pressionando "Montar o componente":

Você verá três logs inicialmente:Schedule "a" log,Cancel "a" log e Schedule "a" lognovamente. Três segundos depois, também haverá um log dizendoa. Como você aprendeu anteriormente, o par extra de agendar/cancelar ocorre porque o React remonta o componente uma vez no desenvolvimento para verificar se você implementou a limpeza corretamente.

Agora edite a entrada para dizerabc. Se você fizer rápido o suficiente, veráSchedule "ab" logimediatamente seguido porCancel "ab" log e Schedule "abc" log.O React sempre limpa o Efeito da renderização anterior antes do Efeito da próxima renderização.É por isso que, mesmo se você digitar rapidamente na entrada, há no máximo um timeout agendado por vez. Edite a entrada algumas vezes e observe o console para sentir como os Efeitos são limpos.

Digite algo na entrada e pressione imediatamente "Desmontar o componente". Observe como a desmontagem limpa o Efeito da última renderização. Aqui, ele limpa o último timeout antes que ele tenha a chance de ser executado.

Finalmente, edite o componente acima e comente a função de limpeza para que os timeouts não sejam cancelados. Tente digitarabcderapidamente. O que você espera que aconteça em três segundos? Oconsole.log(text)dentro do timeout imprimirá oúltimotexte produzirá cinco logs deabcde? Experimente para verificar sua intuição!

Três segundos depois, você deve ver uma sequência de logs (a,ab,abc,abcd e abcde) em vez de cinco logsabcde. Cada Effect “captura” o valor detextda sua renderização correspondente.Não importa que o estadotexttenha mudado: um Effect da renderização comtext = 'ab'sempre verá'ab'. Em outras palavras, os Effects de cada renderização são isolados uns dos outros. Se você está curioso sobre como isso funciona, pode ler sobreclosures.

Deep Dive
Cada renderização tem seus próprios Efeitos

Recapitulação

  • Ao contrário dos eventos, os Efeitos são causados pela própria renderização, e não por uma interação específica.
  • Os Efeitos permitem sincronizar um componente com algum sistema externo (API de terceiros, rede, etc).
  • Por padrão, os Efeitos são executados após cada renderização (incluindo a inicial).
  • O React pulará o Efeito se todas as suas dependências tiverem os mesmos valores da última renderização.
  • Você não pode "escolher" suas dependências. Elas são determinadas pelo código dentro do Efeito.
  • Um array de dependências vazio ([]) corresponde ao "montagem" do componente, ou seja, quando ele é adicionado à tela.
  • No Modo Estrito, o React monta os componentes duas vezes (apenas em desenvolvimento!) para testar a robustez dos seus Efeitos.
  • Se o seu Efeito quebrar devido à remontagem, você precisa implementar uma função de limpeza.
  • O React chamará sua função de limpeza antes do Efeito ser executado na próxima vez e durante a desmontagem.

Try out some challenges

Challenge 1 of 4:Focus a field on mount #

In this example, the form renders a <MyInput /> component.

Use the input’s focus() method to make MyInput automatically focus when it appears on the screen. There is already a commented out implementation, but it doesn’t quite work. Figure out why it doesn’t work, and fix it. (If you’re familiar with the autoFocus attribute, pretend that it does not exist: we are reimplementing the same functionality from scratch.)

To verify that your solution works, press “Show form” and verify that the input receives focus (becomes highlighted and the cursor is placed inside). Press “Hide form” and “Show form” again. Verify the input is highlighted again.

MyInput should only focus on mount rather than after every render. To verify that the behavior is right, press “Show form” and then repeatedly press the “Make it uppercase” checkbox. Clicking the checkbox should not focus the input above it.