Ciclo de Vida dos Efeitos Reativos
Os Efeitos têm um ciclo de vida diferente dos componentes. Os componentes podem montar, atualizar ou desmontar. Um Efeito só pode fazer duas coisas: começar a sincronizar algo e, posteriormente, parar de sincronizá-lo. Esse ciclo pode acontecer várias vezes se o seu Efeito depender de props e estado que mudam ao longo do tempo. O React fornece uma regra de linter para verificar se você especificou corretamente as dependências do seu Efeito. Isso mantém seu Efeito sincronizado com as props e o estado mais recentes.
Você aprenderá
- Como o ciclo de vida de um Efeito é diferente do ciclo de vida de um componente
- Como pensar em cada Efeito individual de forma isolada
- Quando seu Efeito precisa ressincronizar e por quê
- Como as dependências do seu Efeito são determinadas
- O que significa um valor ser reativo
- O que significa um array de dependências vazio
- Como o React verifica se suas dependências estão corretas com um linter
- O que fazer quando você discorda do linter
O ciclo de vida de um Efeito
Todo componente React passa pelo mesmo ciclo de vida:
- Um componentemontaquando é adicionado à tela.
- Um componenteatualizaquando recebe novas props ou estado, geralmente em resposta a uma interação.
- Um componentedesmontaquando é removido da tela.
É uma boa maneira de pensar sobre componentes, masnãosobre Efeitos.Em vez disso, tente pensar em cada Efeito independentemente do ciclo de vida do seu componente. Um Efeito descreve comosincronizar um sistema externocom as props e o estado atuais. Conforme seu código muda, a sincronização precisará acontecer com mais ou menos frequência.
Para ilustrar este ponto, considere este Efeito conectando seu componente a um servidor de chat:
O corpo do seu Efeito especifica comocomeçar a sincronizar:
A função de limpeza retornada pelo seu Efeito especifica comoparar de sincronizar:
Intuitivamente, você pode pensar que o Reactcomeçaria a sincronizarquando seu componente monta epararia de sincronizarquando seu componente desmonta. No entanto, esta não é a história completa! Às vezes, também pode ser necessáriocomeçar e parar de sincronizar várias vezesenquanto o componente permanece montado.
Vamos verpor queisso é necessário,quandoisso acontece ecomovocê pode controlar esse comportamento.
Observação
Alguns Efeitos não retornam uma função de limpeza.Na maioria das vezes,você vai querer retornar uma—mas se não o fizer, o React se comportará como se você tivesse retornado uma função de limpeza vazia.
Por que a sincronização pode precisar acontecer mais de uma vez
Imagine que este componenteChatRoomreceba uma proproomIdque o usuário seleciona em um menu suspenso. Digamos que inicialmente o usuário escolha a sala"general"comoroomId. Seu aplicativo exibe a sala de chat"general":
Após a interface do usuário ser exibida, o React executará seu Efeito paracomeçar a sincronizar.Ele se conecta à sala"general":
Até aqui, tudo bem.
Mais tarde, o usuário seleciona uma sala diferente no menu suspenso (por exemplo,"travel"). Primeiro, o React atualizará a interface do usuário:
Pense no que deve acontecer a seguir. O usuário vê que"travel"é a sala de chat selecionada na interface do usuário. No entanto, o Efeito que foi executado da última vez ainda está conectado à sala"general". A proproomIdmudou, então o que seu Efeito fez naquela ocasião (conectar-se à sala"general") não corresponde mais à interface do usuário.
Neste ponto, você quer que o React faça duas coisas:
- Parar de sincronizar com o antigo
roomId(desconectar da sala"general") - Começar a sincronizar com o novo
roomId(conectar à sala"travel")
Por sorte, você já ensinou ao React como fazer ambas as coisas!O corpo do seu Efeito especifica como começar a sincronizar, e sua função de limpeza especifica como parar de sincronizar. Tudo que o React precisa fazer agora é chamá-las na ordem correta e com as props e o estado corretos. Vamos ver exatamente como isso acontece.
Como o React ressincroniza seu Efeito
Lembre-se de que seu componenteChatRoomrecebeu um novo valor para sua proproomId. Ele costumava ser"general", e agora é"travel". O React precisa ressincronizar seu Efeito para reconectá-lo a uma sala diferente.
Paraparar de sincronizar,o React chamará a função de limpeza que seu Efeito retornou após conectar-se à sala"general". ComoroomIdera"general", a função de limpeza desconecta da sala"general":
Então o React executará o Efeito que você forneceu durante esta renderização. Desta vez,roomId é "travel", então ele irácomeçar a sincronizarcom a sala de chat"travel"(até que sua função de limpeza seja eventualmente chamada também):
Graças a isso, agora você está conectado à mesma sala que o usuário escolheu na interface do usuário. Desastre evitado!
Toda vez que seu componente for renderizado novamente com umroomIddiferente, seu Efeito será ressincronizado. Por exemplo, digamos que o usuário muderoomIdde"travel"para"music". O React irá novamenteparar de sincronizarseu Efeito chamando sua função de limpeza (desconectando você da sala"travel"). Em seguida, ele irácomeçar a sincronizarnovamente executando seu corpo com a nova proproomId(conectando você à sala"music").
Finalmente, quando o usuário vai para uma tela diferente,ChatRoomé desmontado. Agora não há necessidade de permanecer conectado de forma alguma. O React iráparar de sincronizarseu Efeito uma última vez e desconectá-lo da sala de chat"music".
Pensando da perspectiva do Efeito
Vamos recapitular tudo o que aconteceu da perspectiva do componenteChatRoom:
ChatRoommontado comroomIddefinido como"general"ChatRoomatualizado comroomIddefinido como"travel"ChatRoomatualizado comroomIddefinido como"music"ChatRoomdesmontado
Durante cada um desses pontos no ciclo de vida do componente, seu Efeito fez coisas diferentes:
- Seu Efeito conectou-se à sala
"general" - Seu Efeito desconectou-se da sala
"general"e conectou-se à sala"travel" - Seu Efeito desconectou-se da sala
"travel"e conectou-se à sala"music" - Seu Efeito desconectou-se da sala
"music"
Agora vamos pensar sobre o que aconteceu do ponto de vista do próprio Efeito:
A estrutura deste código pode inspirá-lo a ver o que aconteceu como uma sequência de períodos de tempo não sobrepostos:
- Seu Effect conectou-se à sala
"general"(até se desconectar) - Seu Effect conectou-se à sala
"travel"(até se desconectar) - Seu Effect conectou-se à sala
"music"(até se desconectar)
Anteriormente, você estava pensando da perspectiva do componente. Quando você olhava da perspectiva do componente, era tentador pensar nos Effects como "callbacks" ou "eventos de ciclo de vida" que são acionados em um momento específico, como "após uma renderização" ou "antes da desmontagem". Essa forma de pensar fica complicada muito rapidamente, então é melhor evitá-la.
Em vez disso, concentre-se sempre em um único ciclo de início/parada por vez. Não deve importar se um componente está montando, atualizando ou desmontando. Tudo o que você precisa fazer é descrever como iniciar a sincronização e como pará-la. Se você fizer isso bem, seu Effect será resiliente a ser iniciado e parado quantas vezes for necessário.
Isso pode lembrá-lo de como você não pensa se um componente está montando ou atualizando quando escreve a lógica de renderização que cria JSX. Você descreve o que deve estar na tela, e o Reactdescobre o resto.
Como o React verifica que seu Effect pode ser ressincronizado
Aqui está um exemplo ao vivo com o qual você pode interagir. Pressione "Abrir chat" para montar o componenteChatRoom:
Observe que quando o componente monta pela primeira vez, você vê três logs:
✅ Conectando à sala "geral" em https://localhost:1234...(apenas em desenvolvimento)❌ Desconectado da sala "geral" em https://localhost:1234.(apenas em desenvolvimento)✅ Conectando à sala "geral" em https://localhost:1234...
Os dois primeiros logs são apenas em desenvolvimento. Em desenvolvimento, o React sempre remonta cada componente uma vez.
O React verifica que seu Efeito pode ressincronizar forçando-o a fazer isso imediatamente em desenvolvimento.Isso pode lembrar você de abrir uma porta e fechá-la uma vez extra para verificar se a fechadura funciona. O React inicia e para seu Efeito uma vez extra em desenvolvimento para verificarse você implementou sua limpeza corretamente.
O principal motivo pelo qual seu Efeito será ressincronizado na prática é se algum dado que ele usa mudou. Na caixa de areia acima, mude a sala de bate-papo selecionada. Observe como, quando oroomIdmuda, seu Efeito se ressincroniza.
No entanto, também há casos mais incomuns em que a ressincronização é necessária. Por exemplo, tente editar oserverUrlna caixa de areia acima enquanto o bate-papo está aberto. Observe como o Efeito se ressincroniza em resposta às suas edições no código. No futuro, o React pode adicionar mais recursos que dependam da ressincronização.
Como o React sabe que precisa ressincronizar o Efeito
Você pode estar se perguntando como o React soube que seu Efeito precisava ser ressincronizado apósroomIdmudar. É porquevocê disse ao Reactque seu código depende deroomIdincluindo-o nalista de dependências:
Veja como isso funciona:
- Você sabia que
roomIdé uma prop, o que significa que pode mudar ao longo do tempo. - Você sabia que seu Efeito lê
roomId(portanto, sua lógica depende de um valor que pode mudar posteriormente). - É por isso que você o especificou como dependência do seu Efeito (para que ele se ressincronize quando
roomIdmudar).
Toda vez que seu componente for renderizado novamente, o React examinará o array de dependências que você passou. Se algum dos valores no array for diferente do valor na mesma posição que você passou durante a renderização anterior, o React ressincronizará seu Efeito.
Por exemplo, se você passou["general"]durante a renderização inicial e, posteriormente, passou["travel"]durante a próxima renderização, o React comparará"general" e "travel". Esses são valores diferentes (comparados comObject.is), então o React ressincronizará seu Efeito. Por outro lado, se seu componente for renderizado novamente, masroomIdnão tiver mudado, seu Efeito permanecerá conectado à mesma sala.
Cada Efeito representa um processo de sincronização separado
Resista a adicionar lógica não relacionada ao seu Efeito apenas porque essa lógica precisa ser executada ao mesmo tempo que um Efeito que você já escreveu. Por exemplo, digamos que você queira enviar um evento de análise quando o usuário visitar a sala. Você já tem um Efeito que depende deroomId, então você pode sentir a tentação de adicionar a chamada de análise lá:
Mas imagine que você adicione posteriormente outra dependência a este Efeito que precise restabelecer a conexão. Se este Efeito se ressincronizar, ele também chamarálogVisit(roomId)para a mesma sala, o que você não pretendia. Registrar a visitaé um processo separadode conectar. Escreva-os como dois Efeitos separados:
Cada Efeito em seu código deve representar um processo de sincronização separado e independente.
No exemplo acima, excluir um Efeito não quebraria a lógica do outro Efeito. Esta é uma boa indicação de que eles sincronizam coisas diferentes e, portanto, fazia sentido separá-los. Por outro lado, se você dividir uma lógica coesa em Efeitos separados, o código pode parecer “mais limpo”, mas serámais difícil de manter.É por isso que você deve pensar se os processos são iguais ou separados, e não se o código parece mais limpo.
Efeitos “reagem” a valores reativos
Seu Efeito lê duas variáveis (serverUrl e roomId), mas você só especificouroomIdcomo uma dependência:
Por queserverUrlnão precisa ser uma dependência?
Isso ocorre porque oserverUrlnunca muda devido a uma nova renderização. Ele é sempre o mesmo, não importa quantas vezes o componente seja renderizado novamente e por quê. Como oserverUrlnunca muda, não faria sentido especificá-lo como uma dependência. Afinal, as dependências só fazem algo quando mudam ao longo do tempo!
Por outro lado, oroomIdpode ser diferente em uma nova renderização.Props, estado e outros valores declarados dentro do componente sãoreativosporque são calculados durante a renderização e participam do fluxo de dados do React.
SeserverUrlfosse uma variável de estado, ela seria reativa. Valores reativos devem ser incluídos nas dependências:
Ao incluirserverUrlcomo uma dependência, você garante que o Efeito seja ressincronizado após ele mudar.
Tente alterar a sala de bate-papo selecionada ou editar a URL do servidor neste sandbox:
Sempre que você alterar um valor reativo comoroomIdouserverUrl, o Efeito reconecta-se ao servidor de bate-papo.
O que significa um Efeito com dependências vazias
O que acontece se você mover tantoserverUrlquantoroomIdpara fora do componente?
Agora o código do seu Efeito não usanenhumvalor reativo, então suas dependências podem estar vazias ([]).
Pensando da perspectiva do componente, o array de dependências vazio[]significa que este Efeito conecta-se à sala de bate-papo apenas quando o componente é montado, e desconecta-se apenas quando o componente é desmontado. (Lembre-se de que o React aindaressincronizaria ele uma vez extrano desenvolvimento para testar sua lógica.)
No entanto, se vocêpensar da perspectiva do Efeito,não precisa pensar sobre montagem e desmontagem. O importante é que você especificou o que seu Efeito faz para iniciar e parar a sincronização. Hoje, ele não tem dependências reativas. Mas se você quiser que o usuário altere oroomIdou oserverUrlao longo do tempo (e eles se tornariam reativos), o código do seu Efeito não mudará. Você só precisará adicioná-los às dependências.
Todas as variáveis declaradas no corpo do componente são reativas
Props e estado não são os únicos valores reativos. Os valores que você calcula a partir deles também são reativos. Se as props ou o estado mudarem, seu componente será renderizado novamente, e os valores calculados a partir deles também mudarão. É por isso que todas as variáveis do corpo do componente usadas pelo Efeito devem estar na lista de dependências do Efeito.
Digamos que o usuário possa escolher um servidor de bate-papo no menu suspenso, mas também possa configurar um servidor padrão nas configurações. Suponha que você já colocou o estado das configurações em umcontexto, então você lê assettingsdesse contexto. Agora você calcula oserverUrlcom base no servidor selecionado das props e no servidor padrão:
Neste exemplo,serverUrlnão é uma prop ou uma variável de estado. É uma variável regular que você calcula durante a renderização. Mas ela é calculada durante a renderização, então pode mudar devido a uma nova renderização. É por isso que ela é reativa.
Todos os valores dentro do componente (incluindo props, estado e variáveis no corpo do seu componente) são reativos. Qualquer valor reativo pode mudar em uma nova renderização, então você precisa incluir valores reativos como dependências do Effect.
Em outras palavras, os Effects "reagem" a todos os valores do corpo do componente.
O React verifica que você especificou cada valor reativo como uma dependência
Se seu linter estiverconfigurado para React,ele verificará que cada valor reativo usado pelo código do seu Effect é declarado como sua dependência. Por exemplo, este é um erro de lint porque tantoroomIdquantoserverUrlsão reativos:
Isso pode parecer um erro do React, mas na verdade o React está apontando um bug no seu código. TantoroomIdquantoserverUrlpodem mudar com o tempo, mas você está esquecendo de re-sincronizar seu Effect quando eles mudam. Você permanecerá conectado aoroomId e serverUrliniciais mesmo depois que o usuário escolher valores diferentes na interface.
Para corrigir o bug, siga a sugestão do linter para especificarroomId e serverUrlcomo dependências do seu Effect:
Tente esta correção na sandbox acima. Verifique se o erro do linter desapareceu e o chat reconecta quando necessário.
Observação
Em alguns casos, o Reactsabeque um valor nunca muda mesmo que seja declarado dentro do componente. Por exemplo, afunção setretornada poruseStatee o objeto ref retornado poruseRefsãoestáveis—eles têm a garantia de não mudar em uma nova renderização. Valores estáveis não são reativos, então você pode omiti-los da lista. Incluí-los é permitido: eles não mudarão, então não importa.
O que fazer quando você não quer re-sincronizar
No exemplo anterior, você corrigiu o erro do linter listandoroomId e serverUrlcomo dependências.
No entanto, você poderia, em vez disso, "provar" ao linter que esses valores não são valores reativos,ou seja, que elesnão podemmudar como resultado de uma nova renderização. Por exemplo, seserverUrl e roomIdnão dependem da renderização e sempre têm os mesmos valores, você pode movê-los para fora do componente. Agora eles não precisam ser dependências:
Você também pode movê-lospara dentro do Effect.Eles não são calculados durante a renderização, portanto não são reativos:
Effects são blocos de código reativos.Eles ressincronizam quando os valores que você lê dentro deles mudam. Diferente dos manipuladores de eventos, que são executados apenas uma vez por interação, os Effects são executados sempre que a sincronização for necessária.
Você não pode “escolher” suas dependências.Suas dependências devem incluir todos osvalores reativosque você lê no Effect. O linter impõe isso. Às vezes, isso pode levar a problemas como loops infinitos e ao seu Effect ressincronizar com muita frequência. Não corrija esses problemas suprimindo o linter! Veja o que tentar em vez disso:
- Verifique se seu Effect representa um processo de sincronização independente.Se seu Effect não sincroniza nada,ele pode ser desnecessário.Se ele sincroniza várias coisas independentes,divida-o.
- Se você quiser ler o valor mais recente de props ou estado sem “reagir” a ele e ressincronizar o Effect,você pode dividir seu Effect em uma parte reativa (que você manterá no Effect) e uma parte não reativa (que você extrairá para algo chamadoEvento de Effect).Leia sobre separar Eventos de Effects.
- Evite depender de objetos e funções como dependências.Se você criar objetos e funções durante a renderização e depois lê-los de um Effect, eles serão diferentes a cada renderização. Isso fará com que seu Effect ressincronize toda vez.Leia mais sobre remover dependências desnecessárias de Effects.
Armadilha
O linter é seu amigo, mas seus poderes são limitados. O linter só sabe quando as dependências estãoerradas. Ele não sabea melhormaneira de resolver cada caso. Se o linter sugerir uma dependência, mas adicioná-la causar um loop, isso não significa que o linter deva ser ignorado. Você precisa alterar o código dentro (ou fora) do Effect para que esse valor não seja reativo e nãopreciseser uma dependência.
Se você tem uma base de código existente, pode ter alguns Effects que suprimem o linter assim:
Naspróximaspáginas, você aprenderá como corrigir esse código sem quebrar as regras. Sempre vale a pena corrigir!
Recapitulação
- Componentes podem montar, atualizar e desmontar.
- Cada Effect tem um ciclo de vida separado do componente que o envolve.
- Cada Effect descreve um processo de sincronização separado que podeiniciar e parar.
- Ao escrever e ler Effects, pense da perspectiva de cada Effect individual (como iniciar e parar a sincronização) em vez da perspectiva do componente (como ele monta, atualiza ou desmonta).
- Valores declarados dentro do corpo do componente são “reativos”.
- Valores reativos devem ressincronizar o Effect porque podem mudar ao longo do tempo.
- O linter verifica se todos os valores reativos usados dentro do Effect são especificados como dependências.
- Todos os erros sinalizados pelo linter são legítimos. Sempre há uma maneira de corrigir o código para não quebrar as regras.
Try out some challenges
Challenge 1 of 5:Fix reconnecting on every keystroke #
In this example, the ChatRoom component connects to the chat room when the component mounts, disconnects when it unmounts, and reconnects when you select a different chat room. This behavior is correct, so you need to keep it working.
However, there is a problem. Whenever you type into the message box input at the bottom, ChatRoom also reconnects to the chat. (You can notice this by clearing the console and typing into the input.) Fix the issue so that this doesn’t happen.
