Com a introdução de React Hooks, a quantidade de código compartilhável dentro de bases de código React explodiu. Como os Hooks são APIs finas em cima do React, os desenvolvedores podem colaborar anexando comportamento reutilizável a componentes e segregando esses comportamentos em módulos menores.

Embora seja semelhante a como os desenvolvedores de JavaScript abstraem a lógica de negócios em módulos JavaScript vanilla, os Hooks fornecem mais do que funções JavaScript puras. Em vez de inserir e retirar dados, os desenvolvedores podem ampliar o espectro de possibilidades do que pode acontecer dentro de um gancho.

Por exemplo, os desenvolvedores podem:

  • Alterar e gerenciar uma parte do estado de um componente específico ou de um aplicativo inteiro
  • Desencadeia efeitos colaterais em uma página, como alterar o título de uma guia do navegador
  • Rectifique APIs externas explorando o ciclo de vida dos componentes React com ganchos

Nesta postagem, exploraremos a última possibilidade. Como um estudo de caso, vamos abstrair a API MutationObserver em um React Hook personalizado, demonstrando como podemos construir peças de lógica robustas e compartilháveis ​​em uma base de código React.

Criaremos um rótulo dinâmico que se atualiza para indicar quantos itens temos em uma lista. Em vez de usar a matriz de elementos do estado React fornecida, usaremos o MutationObserver API para detectar os elementos adicionados e atualizar o rótulo de acordo.

Atualizar o rótulo dinâmico para contar frutas
Atualize o rótulo dinâmico para contar o número de frutas na lista.

Visão geral da implementação

O código a seguir é um componente simples que renderiza nossa lista. Ele também atualiza um valor de contador que representa o número de frutas atualmente na lista:

 exportar função padrão App () { const listRef=useRef (); const [contagem, setCount]=useState (2); const [frutas, setFruits]=useState (["maçã","pêssego"]); const onListMutation=useCallback ( (mutationList)=> { setCount (mutationList [0].target.children.length); }, [setCount] ); useMutationObservable (listRef.current, onListMutation); Retorna ( 
{`Adicionou $ {count} frutas`}
setFruits ([... frutas, `fruta aleatória $ {frutas.length}`])} > Adicionar frutas aleatórias
    {frutas.map ((f)=> (
  • {f}
  • ))}
); }

Queremos acionar uma função de retorno de chamada sempre que nosso elemento list sofrer mutação. No retorno de chamada a que nos referimos, os filhos do elemento nos fornecem o número de elementos na lista.

Implementando o useMutationObservable gancho personalizado

Vejamos o ponto de integração:

 useMutationObservable (listRef.current, onListMutation);

O Gancho personalizado useMutationObservable acima abstrai as operações necessárias para observar as mudanças no elemento passado como o primeiro parâmetro. Em seguida, ele executa o retorno de chamada passado como o segundo parâmetro sempre que o elemento de destino muda.

Agora, vamos implementar nosso gancho personalizado useMutationObservable .

No Gancho, há várias operações clichê para entender. Primeiro, devemos fornecer um conjunto de opções em conformidade com a API MutationObserver .

Depois que uma instância MutationObserver é criada, devemos chamar observe para ouvir as alterações no elemento DOM de destino.

Quando não precisarmos mais ouvir as mudanças, devemos chamar disconnect no observador para limpar nossa assinatura. Isso deve acontecer quando o componente App for desmontado:

 const DEFAULT_OPTIONS={ config: {atributos: true, childList: true, subtree: true},
};
function useMutationObservable (targetEl, cb, options=DEFAULT_OPTIONS) { const [observador, setObserver]=useState (nulo); useEffect (()=> { const obs=novo MutationObserver (cb); setObserver (obs); }, [cb, opções, setObserver]); useEffect (()=> { if (! observador) return; const {config}=opções; observer.observe (targetEl, config); return ()=> { if (observador) { observer.disconnect (); } }; }, [observador, targetEl, opções]);
}

Todo o trabalho acima, incluindo inicializar o MutationObserver com os parâmetros corretos, observar as mudanças com a chamada para observer.observe e limpar com observer. desconectar , são abstraídos do cliente.

Não apenas exportamos funcionalidade, mas também limpamos ao conectar-se ao ciclo de vida dos componentes React e ao aproveitar callbacks de limpeza em Ganchos de efeito para destruir a instância MutationObserver .

Agora que temos uma versão funcional e básica de nosso Hook, podemos pensar em melhorar sua qualidade iterando em sua API e aprimorando a experiência do desenvolvedor em torno desse código compartilhável.

Validação e desenvolvimento de entrada

Um aspecto importante ao projetar React Hooks customizados é a validação de entrada. Devemos ser capazes de nos comunicar com os desenvolvedores quando as coisas não estão funcionando bem ou quando um determinado caso de uso está chegando a um caso extremo.

Normalmente, os logs de desenvolvimento ajudam os desenvolvedores a entender o código desconhecido para ajustar sua implementação. Da mesma forma, podemos aprimorar a implementação acima adicionando verificações de tempo de execução e logs de aviso abrangentes para validar e comunicar problemas a outros desenvolvedores:

 function useMutationObservable (targetEl, cb, options=DEFAULT_OPTIONS) { const [observador, setObserver]=useState (nulo); useEffect (()=> { //UMA) if (! cb || typeof cb!=="função") { console.warn ( `Você deve fornecer uma função de retorno de chamada válida, em vez disso você forneceu $ {cb}` ); Retorna; } const {debounceTime}=opções; const obs=novo MutationObserver (cb); setObserver (obs); }, [cb, opções, setObserver]); useEffect (()=> { if (! observador) return; if (! targetEl) { //B) console.warn ( `Você deve fornecer um elemento DOM válido para observar, em vez disso, você forneceu $ {targetEl}` ); } const {config}=opções; tentar { observer.observe (targetEl, config); } catch (e) { //C) console.error (e); } return ()=> { if (observador) { observer.disconnect (); } }; }, [observador, targetEl, opções]);
}

Neste exemplo, estamos verificando se um retorno de chamada é passado como um segundo argumento. Essa verificação de API em tempo de execução pode alertar facilmente o desenvolvedor de que algo está errado do lado do chamador.

Também podemos ver se o elemento DOM fornecido é inválido com um valor incorreto fornecido ao Hook em tempo de execução ou não. Eles são registrados juntos para nos informar a fim de resolver o problema rapidamente.

E, se observar gerar um erro, podemos detectá-lo e relatá-lo. Devemos evitar interromper o fluxo de tempo de execução do JavaScript tanto quanto possível, portanto, ao detectar o erro, podemos escolher registrá-lo ou relatá-lo, dependendo do ambiente.

Extensibilidade via configuração

Se quisermos adicionar mais recursos ao nosso Hook, devemos fazer isso de maneira retro-compatível, como um recurso opcional que tem pouco ou nenhum atrito para sua adoção.

Vejamos como podemos opcionalmente eliminar a função de retorno de chamada fornecida para que os chamadores possam especificar um intervalo de tempo quando nenhuma outra alteração no acionador do elemento de destino. Isso executa o retorno de chamada uma vez em vez de executar a mesma quantidade de vezes que o elemento ou seus filhos sofreram mutação:

 import debounce de"lodash.debounce"; const DEFAULT_OPTIONS={ config: {atributos: true, childList: true, subtree: true}, debounceTime: 0
};
function useMutationObservable (targetEl, cb, options=DEFAULT_OPTIONS) { const [observador, setObserver]=useState (nulo); useEffect (()=> { if (! cb || typeof cb!=="função") { console.warn ( `Você deve fornecer uma função de retorno de chamada valida, em vez disso você forneceu $ {cb}` ); Retorna; } const {debounceTime}=opções; const obs=new MutationObserver ( debounceTime> 0? debounce (cb, debounceTime): cb ); setObserver (obs); }, [cb, opções, setObserver]); //...

Isso é útil se precisarmos executar uma operação pesada, como acionar uma solicitação da web, garantindo que seja executada o número mínimo de vezes possível.

Nossa opção debounceTime agora pode passar para nosso Gancho personalizado. Se um valor maior que 0 passa para MutationObservable , o retorno de chamada atrasa de acordo.

Com uma configuração simples exposta em nossa API Hook, permitimos que outros desenvolvedores depurem seus retornos de chamada, o que pode resultar em uma implementação de melhor desempenho, visto que podemos reduzir drasticamente o número de vezes que o código de retorno de chamada é executado.

Claro, podemos sempre eliminar o retorno de chamada do lado do cliente, mas dessa forma enriquecemos nossa API e tornamos a implementação do lado do chamador menor e declarativa.

Teste

O teste é uma parte essencial do desenvolvimento de qualquer tipo de capacidade compartilhada. Isso nos ajuda a garantir um certo nível de qualidade para APIs genéricas quando são fortemente contribuídas e compartilhadas.

O guia para testar React Hooks tem muitos detalhes sobre testes que podem ser implementados neste tutorial.

Documentação

A documentação pode elevar a qualidade dos Ganchos personalizados e torná-los amigáveis ​​para o desenvolvedor.

Mas mesmo ao escrever JavaScript simples, a documentação JSDoc pode ser escrita para APIs Hook customizadas para garantir que o Hook passe a mensagem certa para os desenvolvedores.

Vamos nos concentrar na declaração da função useMutationObservable e como adicionar documentação JSDoc formatada a ela:

/** * Esses ganchos customizados abstraem o uso dos componentes Mutation Observer com React. * Observe as alterações feitas na árvore DOM e acione um retorno de chamada personalizado. * @param {Element} targetEl elemento DOM a ser observado * @param {Function} callback cb que será executado quando houver uma mudança em targetEl ou qualquer * elemento filho (dependendo das opções fornecidas) * @param {Object} options * @param {Object} options.config check \ [options \] (https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe) * @param {number} [options.debounceTime=0] um número que representa a quantidade de tempo em ms * que você deseja debounce a chamada para a função de retorno de chamada fornecida */
function useMutationObservable (targetEl, cb, options=DEFAULT_OPTIONS) {

Escrever isso não é útil apenas para documentação, mas também aproveita os recursos do IntelliSense que autocompletam o uso do Hook e fornecem informações pontuais para os parâmetros do Hook. Isso economiza alguns segundos por uso, potencialmente adicionando horas perdidas na leitura do código e na tentativa de entendê-lo.

Conclusão

Com diferentes tipos de Ganchos personalizados que podemos implementar, vemos como eles integram APIs extrínsecas ao mundo React. É fácil integrar o gerenciamento de estado nos Ganchos e executar efeitos com base nas entradas de componentes usando o Gancho.

Lembre-se de que, para construir ganchos de qualidade, é importante:

  • Projete APIs declarativas e fáceis de usar
  • Aprimore a experiência de desenvolvimento verificando o uso adequado e registrando avisos e erros
  • Expor recursos por meio de configurações, como o exemplo debounceTime
  • Facilite o uso do Hook escrevendo a documentação JSDoc

Você pode verificar a implementação completa do React Hook personalizado aqui .

A postagem Guia para custom React Hooks com MutationObserver apareceu primeiro no LogRocket Blog .