Recentemente, foi lançada a versão 16.8 do React.js, com a qual ganchos se tornaram disponíveis para nós. O conceito de ganchos permite que você escreva componentes funcionais completos usando todos os recursos do React, e permite que você faça isso de muitas maneiras mais convenientemente do que fizemos usando as classes.
Muitos perceberam o surgimento de ganchos com críticas, e neste artigo eu gostaria de falar sobre algumas vantagens importantes que os componentes funcionais com ganchos nos dão e por que devemos mudar para eles.
Não vou me aprofundar nos detalhes do uso de ganchos. Isso não é muito importante para a compreensão dos exemplos deste artigo; basta uma compreensão geral do React. Se você quiser ler exatamente sobre este tópico, as informações sobre ganchos estão na documentação e, se este tópico for interessante, escreverei um artigo com mais detalhes sobre quando, quais e como usar os ganchos corretamente.
Ganchos facilitam a reutilização de código
Vamos imaginar um componente que renderize uma forma simples. Algo que simplesmente produzirá algumas entradas e nos permitirá editá-las.
Algo assim, se bastante simplificado, esse componente se pareceria com uma classe:
class Form extends React.Component { state = { // fields: {}, }; render() { return ( <form> {/* */} </form> ); }; }
Agora imagine que queremos salvar automaticamente os valores dos campos quando eles mudarem. Sugiro omitir declarações de funções adicionais, como shallowEqual
e shallowEqual
.
class Form extends React.Component { constructor(props) { super(props); this.saveToDraft = debounce(500, this.saveToDraft); }; state = { // fields: {}, // , draft: { isSaving: false, lastSaved: null, }, }; saveToDraft = (data) => { if (this.state.isSaving) { return; } this.setState({ isSaving: true, }); makeSomeAPICall().then(() => { this.setState({ isSaving: false, lastSaved: new Date(), }) }); } componentDidUpdate(prevProps, prevState) { if (!shallowEqual(prevState.fields, this.state.fields)) { this.saveToDraft(this.state.fields); } } render() { return ( <form> {/* , */} {/* */} </form> ); }; }
Mesmo exemplo, mas com ganchos:
const Form = () => { // const [fields, setFields] = useState({}); const [draftIsSaving, setDraftIsSaving] = useState(false); const [draftLastSaved, setDraftLastSaved] = useState(false); useEffect(() => { const id = setTimeout(() => { if (draftIsSaving) { return; } setDraftIsSaving(true); makeSomeAPICall().then(() => { setDraftIsSaving(false); setDraftLastSaved(new Date()); }); }, 500); return () => clearTimeout(id); }, [fields]); return ( <form> {/* , */} {/* */} </form> ); }
Como vemos, a diferença ainda não é muito grande. useState
o useState
e useState
com que o salvamento não fosse feito em componentDidUpdate
, mas após renderizar o componente usando o gancho useEffect
.
A diferença que quero mostrar aqui (há outras que discutiremos abaixo): podemos obter esse código e usá-lo em outro local:
// useDraft const useDraft = (fields) => { const [draftIsSaving, setDraftIsSaving] = useState(false); const [draftLastSaved, setDraftLastSaved] = useState(false); useEffect(() => { const id = setTimeout(() => { if (draftIsSaving) { return; } setDraftIsSaving(true); makeSomeAPICall().then(() => { setDraftIsSaving(false); setDraftLastSaved(new Date()); }); }, 500); return () => clearTimeout(id); }, [fields]); return [draftIsSaving, draftLastSaved]; } const Form = () => { // const [fields, setFields] = useState({}); const [draftIsSaving, draftLastSaved] = useDraft(fields); return ( <form> {/* , */} {/* */} </form> ); }
Agora podemos usar o gancho useDraft
que acabamos de escrever em outros componentes! Obviamente, este é um exemplo muito simplificado, mas reutilizar a mesma funcionalidade é um recurso muito útil.
Ganchos permitem que você escreva um código mais intuitivo.
Imagine um componente (por enquanto, na forma de uma classe), que, por exemplo, exibe a janela de bate-papo atual, uma lista de possíveis destinatários e um formulário para enviar uma mensagem. Algo assim:
class ChatApp extends React.Component { state = { currentChat: null, }; handleSubmit = (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(` ${this.state.currentChat} `); }); }; render() { return ( <Fragment> <ChatsList changeChat={currentChat => { this.setState({ currentChat }); }} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={this.handleSubmit} /> </Fragment> ); }; }
O exemplo é muito condicional, mas é bastante adequado para demonstração. Imagine estas ações do usuário:
- Bate-papo aberto 1
- Envie uma mensagem (imagine que a solicitação demore muito)
- Chat aberto 2
- Receba uma mensagem sobre o envio bem-sucedido:
- "Mensagem de bate-papo enviada 2"
Mas a mensagem foi enviada para conversar 1? Isso aconteceu devido ao fato de o método de classe não funcionar com o valor que estava no momento do envio, mas com o que já estava no momento em que a solicitação foi concluída. Isso não seria um problema em um caso tão simples, mas a correção desse comportamento exigirá cuidados adicionais e processamento adicional e, em segundo lugar, pode ser uma fonte de bugs.
No caso de um componente funcional, o comportamento é diferente:
const ChatApp = () => { const [currentChat, setCurrentChat] = useState(null); const handleSubmit = useCallback( (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(` ${currentChat} `); }); }, [currentChat] ); render() { return ( <Fragment> <ChatsList changeChat={setCurrentChat} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={handleSubmit} /> </Fragment> ); }; }
Imagine as mesmas ações do usuário:
- Bate-papo aberto 1
- Envie uma mensagem (a solicitação continua novamente)
- Chat aberto 2
- Receba uma mensagem sobre o envio bem-sucedido:
- "Mensagem de bate-papo enviada 1"
Então o que mudou? O que mudou é que agora, para cada renderização para a qual currentChat
é diferente, estamos criando um novo método. Isso nos permite não pensar se algo vai mudar no futuro - estamos trabalhando com o que temos agora . Cada componente de renderização fecha em si tudo o que está relacionado a ele .
Ganchos nos salvam do ciclo de vida
Este item se sobrepõe ao anterior. React é uma biblioteca para descrever declarativamente uma interface. A declarabilidade facilita muito a escrita e o suporte dos componentes, permite que você pense menos sobre o que teria que ser feito imperativamente se não tivéssemos usado o React.
Apesar disso, ao usar classes, somos confrontados com o ciclo de vida do componente. Se você não for mais fundo, fica assim:
- Montagem de componentes
- Atualização de componente (ao alterar
state
ou props
) - Remoção de componentes
Parece conveniente, mas estou convencido de que é conveniente apenas por causa do hábito. Essa abordagem não é como React.
Em vez disso, componentes funcionais com ganchos nos permitem escrever componentes, pensando não no ciclo de vida, mas na sincronização . Escrevemos a função para que seu resultado reflita exclusivamente o estado da interface, dependendo dos parâmetros externos e do estado interno.
O useEffect
, que muitos percebem como um substituto direto para componentDidMount
, componentDidUpdate
e assim por diante, é realmente destinado a outro. Ao usá-lo, nós meio que dizemos a reação: "Depois de renderizar, execute esses efeitos".
Aqui está um bom exemplo de como o componente funciona com o contador de cliques de um grande artigo sobre useEffect :
- Reagir: Diga-me o que processar com este estado.
- Seu componente:
- Aqui está o resultado da renderização:
<p> 0 </p>
. - E, por favor, execute este efeito quando terminar:
() => { document.title = ' 0 ' }
.
- Reagir: Ok. Atualizando a interface. Ei, Navegador, estou atualizando o DOM
- Navegador: Ótimo, eu desenhei.
- Reagir: Super, agora chamarei o efeito que recebi do componente.
- Começa
() => { document.title = ' 0 ' }
Muito mais declarativo, não é?
Sumário
O React Hooks nos permite livrar-se de alguns problemas e facilitar a compreensão e a codificação dos componentes. Você só precisa mudar o modelo mental que aplicamos a eles. Componentes funcionais são essencialmente funções de interface dos parâmetros. Eles descrevem tudo como deveria ser a qualquer momento e ajudam a não pensar em como responder às mudanças.
Sim, às vezes você precisa aprender a usá-los corretamente , mas, da mesma forma, não aprendemos como usar componentes na forma de classes imediatamente.