Os ganchos entraram em cena com o lançamento de React 16.8 com o grande objetivo de mudar a maneira como escrevemos componentes React. A poeira baixou e os ganchos estão espalhados. O Hooks teve sucesso?

O marketing inicial apresentou o Hooks como uma forma de se livrar dos componentes da classe. O principal problema com os componentes de classe é que a composição é difícil. Compartilhar a lógica contida nos eventos de ciclo de vida componentDidMount e amigos levou a padrões como componentes de ordem superior e renderProps que são padrões estranhos com casos extremos. A melhor coisa sobre os Hooks é sua capacidade de isolar interesses transversais e serem combináveis.

O bom

O que os Hooks fazem bem é encapsular o estado e compartilhar a lógica. Pacotes de biblioteca, como react-router e react-redux têm APIs mais simples e limpas, obrigado para ganchos.

Abaixo está um exemplo de código usando o antigo connect API .

 importar React de'react';
import {Dispatch} de'redux';
importar {conectar} de'react-redux';
import {AppStore, User} de'../types';
importar {ações} de'../actions/constants';
importar {usersSelector} de'../selectors/users'; const mapStateToProps=(state: AppStore)=> ({ usuários: usersSelector (estado)
}); const mapDispatchToProps=(despacho: despacho)=> { Retorna { addItem: (user: User)=> dispatch ({type: actions.ADD_USER, payload: user}) }
} const UsersContainer: React.FC <{users: User [], addItem: (user: User)=> void}>=(props)=> { Retorna ( <> 

Conexão HOC

{ users.map ((usuário)=> { Retorna ( ) }) }
) }; exportar conexão padrão (mapStateToProps, mapDispatchToProps) (UsersContainer);

Um código como este é inchado e repetitivo. Digitar mapStateToProps e mapDispatchToProps era irritante.

Abaixo está o mesmo código refatorado para usar ganchos:

 importar React de'react';
import {useSelector, useDispatch} de'react-redux';
import {AppStore, User} de'../types';
importar {ações} de'../actions/constants'; export const UsersContainer: React.FC=()=> { const dispatch=useDispatch (); usuários const: Usuário []=useSelector ((estado: AppStore)=> estado.usuários); Retorna ( <> 

Ganchos

{ users.map ((usuário)=> { Retorna ( ) }) } ) };

A diferença é noite e dia. Os ganchos fornecem uma API mais limpa e simples. Os ganchos também eliminam a necessidade de envolver tudo em um componente, o que é outra grande vitória.

O ruim

A matriz de dependência

O useEffect Hook leva um argumento de função e um matriz de dependência para o segundo argumento.

 importar React, {useEffect, useState} de'react'; export function Home () { const args=['a']; const [valor, setValue]=useState (['b']); useEffect (()=> { setValue (['c']); }, [args]); console.log ('valor', valor);
}

O código acima fará com que o gancho useEffect gire infinitamente por causa desta atribuição aparentemente inocente:

 const args=['a'];

Em cada nova renderização, o React manterá uma cópia do array de dependência da renderização anterior. O React irá comparar a matriz de dependência atual com a anterior. Cada elemento é comparado usando Método Object.is para determinar se useEffect deve ser executado novamente com os novos valores. Os objetos são comparados por referência e não por valor. A variável args será um novo objeto em cada re-renderização e terá um endereço na memória diferente do anterior.

De repente, as atribuições de variáveis ​​podem ter armadilhas. Infelizmente, existem muitas, muitas armadilhas semelhantes em torno da matriz de dependência. Criar uma função de seta embutida que termina na matriz de dependência levará ao mesmo destino.

A solução, claro, é usar mais ganchos:

 importar React, {useEffect, useState, useRef} de'react'; export function Home () { const [valor, setValue]=useState (['b']); const {current: a}=useRef (['a']) useEffect (()=> { setValue (['c']); }, [uma])
}

É confuso e estranho envolver o código JavaScript padrão em uma infinidade de useRef , useMemo ou useCallback Ganchos. O plugin eslint-plugin-react-hooks faz um trabalho razoável de mantê-lo em linha reta e estreita, mas os bugs não são incomuns, e um plugin ESLint deve ser um suplemento e não obrigatório.

O feio

Publiquei recentemente um react Hook, react-abortable-fetch e envolver tudo em uma combinação de useRef , useCallback ou useMemo não foi uma ótima experiência:

 const [machine, send]=useMachine (createQueryMachine ({initialState})); const abortController=useRef (new AbortController ()); const fetchClient=useRef (createFetchClient  (builderOrRequestInfos, abortController.current)); contador const=useRef (0); tarefa const=useRef  (); tentativas constantes=useRef (0); const timeoutRef=useRef  (timeout ?? undefined); const acumulado=useRef (initialState); const acc=acumulador ?? getDefaultAccumulator (initialState); const abortable=useCallback ( (e: Erro)=> { onAbort (e); enviar (abortar); }, [onAbort, enviar], ); //etc.

A matriz de dependência resultante é muito grande e precisa ser mantida atualizada conforme o código muda, o que é irritante.

}, [ enviar, tempo esgotado, onSuccess, parentOnQuerySuccess, parentOnQueryError, retryAttempts, fetchType, acc, retryDelay, onError, abortável, abortController, ]);

Finalmente, tive que ter cuidado para memorizar o valor de retorno da função Hook usando useMemo e, é claro, fazer malabarismos com outro array de dependência:

 resultado const: QueryResult =useMemo (()=> { switch (machine.value as FetchStates) { case'READY': Retorna { estado:'READY', correr: corredor, redefinir: redefinível, abort: aborter, dados: indefinido, erro: indefinido, counter: counter.current, }; case'LOADING': Retorna { estado:'LOADING', correr: corredor, redefinir: redefinível, abort: aborter, dados: indefinido, erro: indefinido, counter: counter.current, }; caso'SUCEDED': Retorna { estado:'SUCCEEDED', correr: corredor, redefinir: redefinível, abort: aborter, data: machine.context.data, erro: indefinido, counter: counter.current, }; case'ERROR': Retorna { estado:'ERROR', erro: machine.context.error, dados: indefinido, correr: corredor, redefinir: redefinível, abort: aborter, counter: counter.current, }; } }, [machine.value, machine.context.data, machine.context.error, runner, resetable, aborter]); 

Ordem de execução

Os ganchos precisam ser executados na mesma ordem sempre que declarado em “ Regras dos ganchos “:

Não chame Hooks dentro de loops, condições ou funções aninhadas.

Parece muito estranho que os desenvolvedores do React não esperassem ver os Hooks executados em manipuladores de eventos.

A prática comum é retornar uma função de um gancho que pode ser executada fora da ordem dos ganchos:

 const {run, state}=useFetch (`/api/users/1`, {executeOnMount: false}); Retorna (  { corre(); }} > FAÇA 
);

O veredicto

A simplificação do código react-redux mencionado anteriormente é atraente e resulta em uma excelente redução do código de rede. Os ganchos requerem menos código do que os responsáveis ​​anteriores, e isso por si só deve tornar os ganchos um acéfalo.

Os prós do Hooks superam os contras, mas não é uma vitória esmagadora. Ganchos são uma ideia elegante e inteligente, mas podem ser difíceis de usar na prática Gerenciar manualmente o gráfico de dependência e memoizing em todos os lugares certos é provavelmente a origem da maioria dos problemas, e isso poderia ser repensado. As funções do gerador podem ser mais adequadas aqui, com sua capacidade única e bonita de suspender e retomar a execução.

Os fechamentos são o lar de pegadinhas e armadilhas. Um fechamento obsoleto pode fazer referência a variáveis ​​que não estão atualizadas. O conhecimento de fechamentos é uma barreira de entrada ao usar Ganchos, e você deve vir armado com esse conhecimento para depuração.

A postagem React Hooks: o bom , o ruim e o feio apareceram primeiro no LogRocket Blog .