Removendo Dependências de Efeitos
Quando você escreve um Efeito, o linter verifica se você incluiu cada valor reativo (como props e estado) que o Efeito lê na lista de dependências do seu Efeito. Isso garante que seu Efeito permaneça sincronizado com as props e o estado mais recentes do seu componente. Dependências desnecessárias podem fazer com que seu Efeito seja executado com muita frequência ou até mesmo criar um loop infinito. Siga este guia para revisar e remover dependências desnecessárias dos seus Efeitos.
Você aprenderá
- Como corrigir loops infinitos de dependência de Efeitos
- O que fazer quando você quer remover uma dependência
- Como ler um valor do seu Efeito sem “reagir” a ele
- Como e por que evitar dependências de objetos e funções
- Por que suprimir o linter de dependências é perigoso e o que fazer em vez disso
As dependências devem corresponder ao código
Quando você escreve um Efeito, primeiro especifica comoiniciar e pararo que quer que seu Efeito faça:
Então, se você deixar as dependências do Efeito vazias ([]), o linter sugerirá as dependências corretas:
Preencha-as de acordo com o que o linter diz:
Os Efeitos “reagem” a valores reativos.ComoroomIdé um valor reativo (ele pode mudar devido a uma nova renderização), o linter verifica que você o especificou como uma dependência. SeroomIdreceber um valor diferente, o React ressincronizará seu Efeito. Isso garante que o chat permaneça conectado à sala selecionada e “reaja” ao menu suspenso:
Para remover uma dependência, prove que ela não é uma dependência
Observe que você não pode "escolher" as dependências do seu Efeito. Todovalor reativousado pelo código do seu Efeito deve ser declarado na sua lista de dependências. A lista de dependências é determinada pelo código ao redor:
Valores reativosincluem props e todas as variáveis e funções declaradas diretamente dentro do seu componente. ComoroomIdé um valor reativo, você não pode removê-lo da lista de dependências. O linter não permitiria:
E o linter estaria certo! ComoroomIdpode mudar ao longo do tempo, isso introduziria um bug no seu código.
Para remover uma dependência, "prove" ao linter que elanão precisaser uma dependência.Por exemplo, você pode moverroomIdpara fora do seu componente para provar que ele não é reativo e não mudará nas re-renderizações:
Agora queroomIdnão é um valor reativo (e não pode mudar em uma re-renderização), ele não precisa ser uma dependência:
É por isso que agora você poderia especificar umalista de dependências vazia ([]).Seu Efeitorealmente nãodepende mais de nenhum valor reativo, então elerealmente nãoprecisa ser reexecutado quando qualquer prop ou estado do componente mudar.
Para alterar as dependências, altere o código
Você pode ter notado um padrão no seu fluxo de trabalho:
- Primeiro, vocêaltera o códigodo seu Efeito ou como seus valores reativos são declarados.
- Em seguida, você segue o linter e ajusta as dependências paracorresponder ao código que você alterou.
- Se você não estiver satisfeito com a lista de dependências, vocêvolta ao primeiro passo(e altera o código novamente).
A última parte é importante.Se você quiser alterar as dependências, altere primeiro o código ao redor.Você pode pensar na lista de dependências comouma lista de todos os valores reativos usados pelo código do seu Efeito.Você nãoescolheo que colocar nessa lista. A listadescreveseu código. Para alterar a lista de dependências, altere o código.
Isso pode parecer resolver uma equação. Você pode começar com um objetivo (por exemplo, remover uma dependência) e precisa "encontrar" o código que corresponda a esse objetivo. Nem todo mundo acha divertido resolver equações, e o mesmo pode ser dito sobre escrever Efeitos! Felizmente, há uma lista de receitas comuns que você pode experimentar abaixo.
Armadilha
Se você tem uma base de código existente, pode ter alguns Efeitos que suprimem o linter assim:
Quando as dependências não correspondem ao código, há um risco muito alto de introduzir bugs.Ao suprimir o linter, você "mente" para o React sobre os valores dos quais seu Efeito depende.
Em vez disso, use as técnicas abaixo.
Removendo dependências desnecessárias
Sempre que você ajustar as dependências do Efeito para refletir o código, observe a lista de dependências. Faz sentido que o Efeito seja reexecutado quando qualquer uma dessas dependências mudar? Às vezes, a resposta é "não":
- Você pode querer reexecutarpartes diferentesdo seu Efeito sob condições diferentes.
- Você pode querer ler apenas ovalor mais recentede alguma dependência em vez de “reagir” às suas mudanças.
- Uma dependência pode mudar com muita frequênciainadvertidamenteporque é um objeto ou uma função.
Para encontrar a solução correta, você precisará responder a algumas perguntas sobre o seu Efeito. Vamos analisá-las.
Este código deve ser movido para um manipulador de eventos?
A primeira coisa que você deve pensar é se este código deveria ser um Efeito em primeiro lugar.
Imagine um formulário. Ao enviar, você define a variável de estadosubmitted como true. Você precisa enviar uma requisição POST e mostrar uma notificação. Você colocou essa lógica dentro de um Efeito que “reage” asubmittedsertrue:
Mais tarde, você quer estilizar a mensagem da notificação de acordo com o tema atual, então você lê o tema atual. Comothemeé declarado no corpo do componente, é um valor reativo, então você o adiciona como uma dependência:
Ao fazer isso, você introduziu um bug. Imagine que você envia o formulário primeiro e depois alterna entre os temas Escuro e Claro. Othememudará, o Efeito será reexecutado e, portanto, exibirá a mesma notificação novamente!
O problema aqui é que isso não deveria ser um Efeito em primeiro lugar.Você quer enviar esta requisição POST e mostrar a notificação em resposta aoenvio do formulário,que é uma interação específica. Para executar algum código em resposta a uma interação específica, coloque essa lógica diretamente no manipulador de eventos correspondente:
Agora que o código está em um manipulador de eventos, ele não é reativo—portanto, só será executado quando o usuário enviar o formulário. Leia mais sobreescolher entre manipuladores de eventos e Efeitos e como excluir Efeitos desnecessários.
Seu Efeito está fazendo várias coisas não relacionadas?
A próxima pergunta que você deve se fazer é se o seu Efeito está fazendo várias coisas não relacionadas.
Imagine que você está criando um formulário de envio onde o usuário precisa escolher sua cidade e área. Você busca a lista decitiesdo servidor de acordo com ocountryselecionado para mostrá-las em uma lista suspensa:
Este é um bom exemplo debusca de dados em um Efeito.Você está sincronizando o estadocitiescom a rede de acordo com a propcountry. Você não pode fazer isso em um manipulador de eventos porque precisa buscar assim queShippingFormfor exibido e sempre que ocountrymudar (não importa qual interação cause isso).
Agora digamos que você está adicionando uma segunda caixa de seleção para áreas da cidade, que deve buscar asareas para a cityatualmente selecionada. Você pode começar adicionando uma segunda chamadafetchpara a lista de áreas dentro do mesmo Efeito:
No entanto, como o Effect agora usa a variável de estadocity, você teve que adicionarcityà lista de dependências. Isso, por sua vez, introduziu um problema: quando o usuário seleciona uma cidade diferente, o Effect será executado novamente e chamaráfetchCities(country). Como resultado, você estará buscando desnecessariamente a lista de cidades várias vezes.
O problema com este código é que você está sincronizando duas coisas diferentes e não relacionadas:
- Você deseja sincronizar o estado
citiescom a rede com base na propcountry. - Você deseja sincronizar o estado
areascom a rede com base no estadocity.
Divida a lógica em dois Effects, cada um reagindo à prop com a qual precisa sincronizar:
Agora o primeiro Effect só será executado novamente se ocountrymudar, enquanto o segundo Effect será executado novamente quando ocitymudar. Você os separou por propósito: duas coisas diferentes são sincronizadas por dois Effects separados. Dois Effects separados têm duas listas de dependências separadas, então eles não acionarão um ao outro inadvertidamente.
O código final é mais longo que o original, mas dividir esses Effects ainda está correto.Cada Effect deve representar um processo de sincronização independente.Neste exemplo, excluir um Effect não quebra a lógica do outro Effect. Isso significa que elessincronizam coisas diferentes,e é bom separá-los. Se você está preocupado com duplicação, pode melhorar este códigoextraindo a lógica repetitiva em um Hook personalizado.
Você está lendo algum estado para calcular o próximo estado?
Este Effect atualiza a variável de estadomessagescom um array recém-criado toda vez que uma nova mensagem chega:
Ele usa a variávelmessagesparacriar um novo arraycomeçando com todas as mensagens existentes e adiciona a nova mensagem no final. No entanto, comomessagesé um valor reativo lido por um Effect, ele deve ser uma dependência:
E tornarmessagesuma dependência introduz um problema.
Toda vez que você recebe uma mensagem,setMessages()faz com que o componente seja renderizado novamente com um novo arraymessagesque inclui a mensagem recebida. No entanto, como este Effect agora depende demessages, issotambémressincronizará o Effect. Portanto, cada nova mensagem fará o chat reconectar. O usuário não gostaria disso!
Para corrigir o problema, não leiamessagesdentro do Effect. Em vez disso, passe umafunção de atualizaçãoparasetMessages:
Observe como seu Efeito agora não lê a variávelmessagesde forma alguma.Você só precisa passar uma função de atualização comomsgs => [...msgs, receivedMessage]. O Reactcoloca sua função de atualização em uma filae fornecerá o argumentomsgspara ela durante a próxima renderização. É por isso que o próprio Efeito não precisa mais depender demessages. Como resultado dessa correção, receber uma mensagem de chat não fará mais o chat se reconectar.
Você quer ler um valor sem “reagir” às suas mudanças?
Suponha que você queira tocar um som quando o usuário recebe uma nova mensagem, a menos queisMutedsejatrue:
Como seu Efeito agora usaisMutedem seu código, você precisa adicioná-lo às dependências:
O problema é que toda vez queisMutedmuda (por exemplo, quando o usuário pressiona o botão “Mudo”), o Efeito será ressincronizado e reconectará ao chat. Essa não é a experiência de usuário desejada! (Neste exemplo, mesmo desabilitar o linter não funcionaria—se você fizer isso,isMutedficaria “preso” com seu valor antigo.)
Para resolver esse problema, você precisa extrair a lógica que não deve ser reativa do Efeito. Você não quer que este Efeito “reaja” às mudanças emisMuted.Mova essa parte não reativa da lógica para um Evento de Efeito:
Eventos de Efeito permitem que você divida um Efeito em partes reativas (que devem “reagir” a valores reativos comoroomIde suas mudanças) e partes não reativas (que apenas leem seus valores mais recentes, comoonMessage lê isMuted).Agora que você lêisMuteddentro de um Evento de Efeito, ele não precisa ser uma dependência do seu Efeito.Como resultado, o chat não se reconectará quando você alternar a configuração “Mudo” ligada e desligada, resolvendo o problema original!
Envolvendo um manipulador de eventos das props
Você pode encontrar um problema semelhante quando seu componente recebe um manipulador de eventos como prop:
Suponha que o componente pai passe uma funçãodiferenteonReceiveMessageem cada renderização:
ComoonReceiveMessageé uma dependência, isso faria com que o Efeito fosse ressincronizado após cada re-renderização do pai. Isso faria com que ele se reconectasse ao chat. Para resolver isso, envolva a chamada em um Evento de Efeito:
Eventos de Efeito não são reativos, então você não precisa especificá-los como dependências. Como resultado, o chat não se reconectará mais mesmo se o componente pai passar uma função diferente em cada re-renderização.
Separando código reativo e não reativo
Neste exemplo, você quer registrar uma visita toda vez queroomIdmuda. Você quer incluir anotificationCountatual em cada registro, mas vocênãoquer que uma mudança emnotificationCountdispare um evento de registro.
A solução, novamente, é separar o código não reativo em um Evento de Efeito:
Você quer que sua lógica seja reativa em relação aroomId, então você lêroomIddentro do seu Efeito. No entanto, você não quer que uma mudança emnotificationCountregistre uma visita extra, então você lênotificationCountdentro do Evento de Efeito.Saiba mais sobre como ler as props e o estado mais recentes dos Efeitos usando Eventos de Efeito.
Algum valor reativo muda de forma não intencional?
Às vezes, vocêrealmentequer que seu Efeito "reaja" a um determinado valor, mas esse valor muda com mais frequência do que você gostaria — e pode não refletir nenhuma mudança real da perspectiva do usuário. Por exemplo, digamos que você crie um objetooptionsno corpo do seu componente e, em seguida, leia esse objeto de dentro do seu Efeito:
Este objeto é declarado no corpo do componente, então é umvalor reativo.Quando você lê um valor reativo como este dentro de um Efeito, você o declara como uma dependência. Isso garante que seu Efeito "reaja" às suas mudanças:
É importante declará-lo como uma dependência! Isso garante, por exemplo, que se oroomIdmudar, seu Efeito se reconectará ao chat com as novasoptions. No entanto, também há um problema com o código acima. Para vê-lo, tente digitar na entrada no sandbox abaixo e observe o que acontece no console:
No sandbox acima, a entrada apenas atualiza a variável de estadomessage. Da perspectiva do usuário, isso não deveria afetar a conexão do chat. No entanto, toda vez que você atualiza amessage, seu componente é renderizado novamente. Quando seu componente é renderizado novamente, o código dentro dele é executado novamente do zero.
Um novo objetooptionsé criado do zero em cada nova renderização do componenteChatRoom. O React vê que o objetooptionsé umobjeto diferentedo objetooptionscriado durante a última renderização. É por isso que ele ressincroniza seu Efeito (que depende deoptions), e o chat se reconecta conforme você digita.
Este problema afeta apenas objetos e funções. Em JavaScript, cada objeto e função recém-criados são considerados distintos de todos os outros. Não importa que o conteúdo dentro deles possa ser o mesmo!
Dependências de objetos e funções podem fazer seu Effect ser ressincronizado com mais frequência do que o necessário.
É por isso que, sempre que possível, você deve tentar evitar objetos e funções como dependências do seu Effect. Em vez disso, tente movê-los para fora do componente, para dentro do Effect, ou extraia valores primitivos deles.
Mova objetos e funções estáticos para fora do seu componente
Se o objeto não depende de nenhuma prop ou estado, você pode mover esse objeto para fora do seu componente:
Dessa forma, vocêcomprovapara o linter que ele não é reativo. Ele não pode mudar como resultado de uma nova renderização, então não precisa ser uma dependência. Agora, renderizar novamente oChatRoomnão fará seu Effect ser ressincronizado.
Isso também funciona para funções:
ComocreateOptionsé declarada fora do seu componente, ela não é um valor reativo. É por isso que ela não precisa ser especificada nas dependências do seu Effect e nunca fará seu Effect ser ressincronizado.
Mova objetos e funções dinâmicos para dentro do seu Effect
Se o seu objeto depende de algum valor reativo que pode mudar como resultado de uma nova renderização, como uma proproomId, você não pode tirá-lopara forado seu componente. No entanto, você pode mover sua criaçãopara dentrodo código do seu Effect:
Agora queoptionsé declarada dentro do seu Effect, ela não é mais uma dependência do seu Effect. Em vez disso, o único valor reativo usado pelo seu Effect éroomId. ComoroomIdnão é um objeto ou função, você pode ter certeza de que ele não seráinadvertidamentediferente. Em JavaScript, números e strings são comparados pelo seu conteúdo:
Graças a essa correção, o chat não se reconecta mais se você editar a entrada:
No entanto, elerealmentereconecta quando você altera o menu suspensoroomId, como seria de se esperar.
Isso também funciona para funções:
Você pode escrever suas próprias funções para agrupar partes da lógica dentro do seu Efeito. Desde que você também as declaredentrodo seu Efeito, elas não são valores reativos e, portanto, não precisam ser dependências do seu Efeito.
Ler valores primitivos de objetos
Às vezes, você pode receber um objeto das props:
O risco aqui é que o componente pai criará o objeto durante a renderização:
Isso faria com que seu Efeito se reconectasse toda vez que o componente pai fosse renderizado novamente. Para corrigir isso, leia as informações do objetoforado Efeito e evite ter dependências de objetos e funções:
A lógica fica um pouco repetitiva (você lê alguns valores de um objeto fora de um Efeito e depois cria um objeto com os mesmos valores dentro do Efeito). Mas isso torna muito explícito de quais informações seu Efeitorealmentedepende. Se um objeto for recriado inadvertidamente pelo componente pai, o chat não se reconectaria. No entanto, seoptions.roomIdouoptions.serverUrlrealmente forem diferentes, o chat se reconectaria.
Calcular valores primitivos de funções
A mesma abordagem pode funcionar para funções. Por exemplo, suponha que o componente pai passe uma função:
Para evitar torná-la uma dependência (e causar sua reconexão em novas renderizações), chame-a fora do Efeito. Isso fornece os valoresroomId e serverUrlque não são objetos e que você pode ler de dentro do seu Efeito:
Isso só funciona para funçõespurasporque elas são seguras para chamar durante a renderização. Se sua função for um manipulador de eventos, mas você não quiser que suas alterações ressincronizem seu Efeito,envolva-a em um Evento de Efeito.
Recapitulação
- As dependências devem sempre corresponder ao código.
- Quando você não está satisfeito com suas dependências, o que você precisa editar é o código.
- Suprimir o linter leva a bugs muito confusos, e você deve sempre evitá-lo.
- Para remover uma dependência, você precisa "provar" ao linter que ela não é necessária.
- Se algum código deve ser executado em resposta a uma interação específica, mova esse código para um manipulador de eventos.
- Se diferentes partes do seu Efeito devem ser reexecutadas por motivos diferentes, divida-o em vários Efeitos.
- Se você quiser atualizar algum estado com base no estado anterior, passe uma função de atualização.
- Se você quiser ler o valor mais recente sem "reagir" a ele, extraia um Evento de Efeito do seu Efeito.
- Em JavaScript, objetos e funções são considerados diferentes se foram criados em momentos diferentes.
- Tente evitar dependências de objetos e funções. Mova-os para fora do componente ou para dentro do Efeito.
Try out some challenges
Challenge 1 of 4:Fix a resetting interval #
This Effect sets up an interval that ticks every second. You’ve noticed something strange happening: it seems like the interval gets destroyed and re-created every time it ticks. Fix the code so that the interval doesn’t get constantly re-created.
