v19.2Latest

Reutilizando Lógica com Hooks Personalizados

O React vem com vários Hooks integrados comouseState,useContext e useEffect. Às vezes, você pode desejar que existisse um Hook para algum propósito mais específico: por exemplo, para buscar dados, para rastrear se o usuário está online ou para conectar-se a uma sala de chat. Você pode não encontrar esses Hooks no React, mas pode criar seus próprios Hooks para as necessidades da sua aplicação.

Você aprenderá
  • O que são Hooks personalizados e como escrever os seus
  • Como reutilizar lógica entre componentes
  • Como nomear e estruturar seus Hooks personalizados
  • Quando e por que extrair Hooks personalizados

Hooks Personalizados: Compartilhando lógica entre componentes

Imagine que você está desenvolvendo um aplicativo que depende muito da rede (como a maioria dos aplicativos). Você quer alertar o usuário se a conexão de rede dele foi acidentalmente desligada enquanto ele usava seu aplicativo. Como você faria isso? Parece que você precisará de duas coisas no seu componente:

  1. Um pedaço de estado que rastreia se a rede está online.
  2. Um Efeito que se inscreve nos eventos globaisonline e offline, e atualiza esse estado.

Isso manterá seu componentesincronizadocom o status da rede. Você pode começar com algo assim:

Tente ligar e desligar sua rede e observe como estaStatusBaratualiza em resposta às suas ações.

Agora imagine que vocêtambémqueira usar a mesma lógica em um componente diferente. Você quer implementar um botão Salvar que ficará desabilitado e mostrará “Reconectando…” em vez de “Salvar” enquanto a rede estiver desligada.

Para começar, você pode copiar e colar o estadoisOnlinee o Efeito noSaveButton:

Verifique que, se você desligar a rede, o botão mudará sua aparência.

Esses dois componentes funcionam bem, mas a duplicação de lógica entre eles é lamentável. Parece que, embora tenham umaaparência visual diferente,você deseja reutilizar a lógica entre eles.

Extraindo seu próprio Hook personalizado de um componente

Imagine por um momento que, semelhante aouseStatee aouseEffect, houvesse um Hook integrado chamadouseOnlineStatus. Então, ambos os componentes poderiam ser simplificados e você poderia remover a duplicação entre eles:

Embora não exista um Hook integrado como esse, você pode escrevê-lo você mesmo. Declare uma função chamadauseOnlineStatuse mova todo o código duplicado para ela a partir dos componentes que você escreveu anteriormente:

No final da função, retorneisOnline. Isso permite que seus componentes leiam esse valor:

Verifique que ligar e desligar a rede atualiza ambos os componentes.

Agora seus componentes não têm tanta lógica repetitiva.Mais importante, o código dentro deles descreveo que eles querem fazer(usar o status online!) em vez decomo fazer(inscrevendo-se nos eventos do navegador).

Quando você extrai a lógica em Hooks personalizados, pode ocultar os detalhes complicados de como você lida com algum sistema externo ou uma API do navegador. O código dos seus componentes expressa sua intenção, não a implementação.

Os nomes dos Hooks sempre começam comuse

Aplicações React são construídas a partir de componentes. Componentes são construídos a partir de Hooks, sejam eles integrados ou personalizados. Você provavelmente usará frequentemente Hooks personalizados criados por outras pessoas, mas ocasionalmente pode escrever um você mesmo!

Você deve seguir estas convenções de nomenclatura:

  1. Os nomes dos componentes React devem começar com uma letra maiúscula,comoStatusBar e SaveButton. Componentes React também precisam retornar algo que o React saiba exibir, como um pedaço de JSX.
  2. Os nomes dos Hooks devem começar comuseseguido de uma letra maiúscula,comouseState(integrado) ouuseOnlineStatus(personalizado, como anteriormente na página). Hooks podem retornar valores arbitrários.

Esta convenção garante que você sempre pode olhar para um componente e saber onde seu estado, Efeitos e outros recursos do React podem estar "escondidos". Por exemplo, se você vir uma chamada de funçãogetColor()dentro do seu componente, você pode ter certeza de que ela não pode conter estado do React dentro porque seu nome não começa comuse. No entanto, uma chamada de função comouseOnlineStatus()provavelmente conterá chamadas para outros Hooks dentro!

Observação

Se o seu linter estiverconfigurado para React,ele imporá esta convenção de nomenclatura. Role para cima até a sandbox acima e renomeieuseOnlineStatusparagetOnlineStatus. Observe que o linter não permitirá que você chameuseStateouuseEffectdentro dele. Apenas Hooks e componentes podem chamar outros Hooks!

Deep Dive
Todas as funções chamadas durante a renderização devem começar com o prefixo use?

Custom Hooks permitem compartilhar lógica com estado, não o estado em si

No exemplo anterior, quando você ligou e desligou a rede, ambos os componentes foram atualizados juntos. No entanto, é errado pensar que uma única variável de estadoisOnlineé compartilhada entre eles. Veja este código:

Funciona da mesma forma que antes de você extrair a duplicação:

São duas variáveis de estado e Effects completamente independentes! Elas aconteceram de ter o mesmo valor ao mesmo tempo porque você as sincronizou com o mesmo valor externo (se a rede está ligada).

Para ilustrar isso melhor, precisaremos de um exemplo diferente. Considere este componenteForm:

Há alguma lógica repetitiva para cada campo do formulário:

  1. Há um pedaço de estado (firstName e lastName).
  2. Há um manipulador de alteração (handleFirstNameChange e handleLastNameChange).
  3. Há um pedaço de JSX que especifica os atributosvalue e onChangepara aquele input.

Você pode extrair a lógica repetitiva neste custom HookuseFormInput:

Observe que ele declara apenasumavariável de estado chamadavalue.

No entanto, o componenteForm chama useFormInputduas vezes:

É por isso que funciona como declarar duas variáveis de estado separadas!

Os Hooks personalizados permitem que você compartilhelógica com estado, mas não oestado em si.Cada chamada a um Hook é completamente independente de qualquer outra chamada ao mesmo Hook.É por isso que os dois sandboxes acima são completamente equivalentes. Se quiser, role para cima e compare-os. O comportamento antes e depois de extrair um Hook personalizado é idêntico.

Quando você precisa compartilhar o estado em si entre vários componentes,eleve-o e passe-o para baixoem vez disso.

Passando valores reativos entre Hooks

O código dentro dos seus Hooks personalizados será reexecutado durante cada nova renderização do seu componente. É por isso que, assim como os componentes, os Hooks personalizadosprecisam ser puros.Pense no código dos Hooks personalizados como parte do corpo do seu componente!

Como os Hooks personalizados são renderizados novamente junto com o seu componente, eles sempre recebem os props e o estado mais recentes. Para ver o que isso significa, considere este exemplo de sala de bate-papo. Altere a URL do servidor ou a sala de bate-papo:

Quando você alteraserverUrlouroomId, o Efeito“reage” às suas alteraçõese ressincroniza. Você pode perceber pelas mensagens no console que o chat reconecta toda vez que você altera as dependências do seu Efeito.

Agora mova o código do Efeito para um Hook personalizado:

Isso permite que seu componenteChatRoomchame seu Hook personalizado sem se preocupar com seu funcionamento interno:

Isso parece muito mais simples! (Mas faz a mesma coisa.)

Observe que a lógicaainda respondea mudanças nas props e no estado. Tente editar a URL do servidor ou a sala selecionada:

Observe como você está pegando o valor de retorno de um Hook:

e passando-o como entrada para outro Hook:

Toda vez que seu componenteChatRoomé renderizado novamente, ele passa os valores mais recentes deroomId e serverUrlpara o seu Hook. É por isso que seu Effect reconecta ao chat sempre que seus valores são diferentes após uma nova renderização. (Se você já trabalhou com software de processamento de áudio ou vídeo, encadear Hooks assim pode lembrá-lo de encadear efeitos visuais ou de áudio. É como se a saída douseState"alimentasse" a entrada douseChatRoom.)

Passando manipuladores de eventos para Hooks personalizados

À medida que você começa a usaruseChatRoomem mais componentes, você pode querer permitir que os componentes personalizem seu comportamento. Por exemplo, atualmente, a lógica do que fazer quando uma mensagem chega está codificada dentro do Hook:

Digamos que você queira mover essa lógica de volta para o seu componente:

Para fazer isso funcionar, altere seu Hook personalizado para aceitaronReceiveMessagecomo uma de suas opções nomeadas:

Isso funcionará, mas há mais uma melhoria que você pode fazer quando seu Hook personalizado aceita manipuladores de eventos.

Adicionar uma dependência emonReceiveMessagenão é ideal porque fará o chat reconectar toda vez que o componente for renderizado novamente.Envolva este manipulador de eventos em um Evento de Efeito para removê-lo das dependências:

Agora o chat não reconectará toda vez que o componenteChatRoomfor renderizado novamente. Aqui está uma demonstração totalmente funcional de como passar um manipulador de eventos para um Hook personalizado com a qual você pode interagir:

Observe que você não precisa mais sabercomouseChatRoomfunciona para usá-lo. Você poderia adicioná-lo a qualquer outro componente, passar quaisquer outras opções, e ele funcionaria da mesma forma. Esse é o poder dos Hooks personalizados.

Quando usar Hooks personalizados

Você não precisa extrair um Hook personalizado para cada pequeno trecho de código duplicado. Um pouco de duplicação é aceitável. Por exemplo, extrair um HookuseFormInputpara encapsular uma única chamadauseStatecomo feito anteriormente provavelmente é desnecessário.

No entanto, sempre que você escrever um Effect, considere se seria mais claro também envolvê-lo em um Hook personalizado.Você não deveria precisar de Effects com muita frequência,então, se você está escrevendo um, significa que precisa "sair do React" para sincronizar com algum sistema externo ou fazer algo para o qual o React não tem uma API integrada. Envolvê-lo em um Hook personalizado permite que você comunique precisamente sua intenção e como os dados fluem através dele.

Por exemplo, considere um componenteShippingFormque exibe duas listas suspensas: uma mostra a lista de cidades e outra mostra a lista de áreas na cidade selecionada. Você pode começar com um código semelhante a este:

Embora este código seja bastante repetitivo,é correto manter esses Effects separados um do outro.Eles sincronizam duas coisas diferentes, então você não deve mesclá-los em um único Effect. Em vez disso, você pode simplificar o componenteShippingFormacima extraindo a lógica comum entre eles em seu próprio HookuseData:

Agora você pode substituir ambos os Effects nos componentesShippingFormpor chamadas parauseData:

Extrair um Hook personalizado torna o fluxo de dados explícito. Você fornece aurle rece

Deep Dive
Mantenha seus Hooks personalizados focados em casos de uso concretos de alto nível

Custom Hooks ajudam você a migrar para padrões melhores

Os Efeitos são uma“saída de emergência”: você os usa quando precisa “sair do React” e quando não há uma solução embutida melhor para o seu caso de uso. Com o tempo, o objetivo da equipe do React é reduzir o número de Efeitos no seu aplicativo ao mínimo, fornecendo soluções mais específicas para problemas mais específicos. Envolver seus Efeitos em Custom Hooks facilita a atualização do seu código quando essas soluções se tornarem disponíveis.

Vamos voltar a este exemplo:

No exemplo acima,useOnlineStatusé implementado com um par deuseState e useEffect.No entanto, esta não é a melhor solução possível. Há uma série de casos extremos que ela não considera. Por exemplo, ela assume que quando o componente monta,isOnlinejá étrue, mas isso pode estar errado se a rede já estiver offline. Você pode usar a API do navegadornavigator.onLinepara verificar isso, mas usá-la diretamente não funcionaria no servidor para gerar o HTML inicial. Em resumo, este código poderia ser melhorado.

O React inclui uma API dedicada chamadauseSyncExternalStoreque cuida de todos esses problemas para você. Aqui está o seu HookuseOnlineStatus, reescrito para aproveitar esta nova API:

Observe comovocê não precisou alterar nenhum dos componentespara fazer essa migração:

Essa é outra razão pela qual encapsular Effects em Hooks personalizados é frequentemente benéfico:

  1. Você torna o fluxo de dados de e para seus Effects muito explícito.
  2. Você permite que seus componentes se concentrem na intenção, em vez da implementação exata de seus Effects.
  3. Quando o React adiciona novos recursos, você pode remover esses Effects sem alterar nenhum de seus componentes.

Semelhante a umsistema de design,você pode achar útil começar a extrair expressões idiomáticas comuns dos componentes do seu aplicativo em Hooks personalizados. Isso manterá o código dos seus componentes focado na intenção e permitirá que você evite escrever Effects brutos com frequência. Muitos Hooks personalizados excelentes são mantidos pela comunidade React.

Deep Dive
O React fornecerá alguma solução integrada para busca de dados?

Existe mais de uma maneira de fazer isso

Digamos que você queira implementar uma animação de fade-indo zerousando a API do navegadorrequestAnimationFrame. Você pode começar com um Effect que configura um loop de animação. Durante cada quadro da animação, você poderia alterar a opacidade do nó DOM que vocêmantém em uma refaté que ela atinja1. Seu código pode começar assim:

Para tornar o componente mais legível, você pode extrair a lógica em um Hook personalizadouseFadeIn:

Você poderia manter o código deuseFadeIncomo está, mas também poderia refatorá-lo mais. Por exemplo, você poderia extrair a lógica para configurar o loop de animação deuseFadeInem um Hook personalizadouseAnimationLoop:

No entanto, você nãoprecisavafazer isso. Como em funções regulares, no final das contas você decide onde traçar os limites entre as diferentes partes do seu código. Você também poderia adotar uma abordagem muito diferente. Em vez de manter a lógica no Efeito, você poderia mover a maior parte da lógica imperativa para dentro de umaclasse JavaScript:

Os Efeitos permitem que você conecte o React a sistemas externos. Quanto mais coordenação entre Efeitos for necessária (por exemplo, para encadear várias animações), mais sentido faz extrair essa lógica completamente dos Efeitos e Hooks,completamentecomo na sandbox acima. Então, o código que você extraiuse tornao "sistema externo". Isso permite que seus Efeitos permaneçam simples, pois eles só precisam enviar mensagens para o sistema que você moveu para fora do React.

Os exemplos acima assumem que a lógica de fade-in precisa ser escrita em JavaScript. No entanto, essa animação de fade-in específica é mais simples e muito mais eficiente de implementar com uma simplesAnimação CSS:

Às vezes, você nem precisa de um Hook!

Recapitulação

  • Hooks personalizados permitem que você compartilhe lógica entre componentes.
  • Hooks personalizados devem ser nomeados começando comuse

Try out some challenges

Challenge 1 of 5:Extract a useCounter Hook #

This component uses a state variable and an Effect to display a number that increments every second. Extract this logic into a custom Hook called useCounter. Your goal is to make the Counter component implementation look exactly like this:

export default function Counter() {
  const count = useCounter();
  return <h1>Seconds passed: {count}</h1>;
}

You’ll need to write your custom Hook in useCounter.js and import it into the App.js file.