Quando o React.js 16.8 foi lançado, tivemos a oportunidade de usar o React Hooks. Ganchos nos permitem escrever componentes totalmente funcionais usando funções. Podemos usar todos os recursos do React.js e fazê-lo da maneira mais conveniente.
Muitas pessoas não concordam com a concepção de Hooks. Neste artigo, gostaria de falar sobre algumas vantagens importantes que o React Hooks oferece e por que precisamos escrever com Hooks.
Não vou falar sobre como usar ganchos. Não é muito importante para os exemplos. Se você quiser ler algo sobre este tópico, poderá usar a documentação oficial . Além disso, se este tópico for interessante para você, escreverei mais sobre Hooks.
Ganchos nos permitem reutilizar nosso código facilmente
Vamos imaginar um componente processando um formulário simples. Pode ser algo que nos mostra algumas entradas e nos permite alterar seus valores.
Com a notação de classe, haverá algo como isto:
class Form extends React.Component { state = { // Fields values fields: {}, }; render() { return ( <form> {/* Inputs render */} </form> ); }; }
Vamos imaginar agora que queremos salvar automaticamente os valores de nossos campos em um back-end sempre que eles forem alterados. Sugiro pular a definição de funções externas como shallowEqual
e shallowEqual
.
class Form extends React.Component { constructor(props) { super(props); this.saveToDraft = debounce(500, this.saveToDraft); }; state = { // Fields values fields: {}, // Draft saving meta 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> {/* Draft saving meta render */} {/* Inputs render */} </form> ); }; }
O mesmo componente com ganchos:
const Form = () => { // Our state 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> {/* Draft saving meta render */} {/* Inputs render */} </form> ); }
Como vemos, não há uma grande diferença aqui. Substituímos this.state
por useState
hook e useState
o rascunho em useEffect
hook agora.
A diferença que quero mostrar aqui é (há outras diferenças também, mas vou me concentrar nessa): podemos facilmente extrair esse código do nosso componente e usá-lo em outro lugar:
// useDraft hook can be used in any other component 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 = () => { // Our state const [fields, setFields] = useState({}); const [draftIsSaving, draftLastSaved] = useDraft(fields); return ( <form> {/* Draft saving meta render */} {/* Inputs render */} </form> ); }
E podemos usar o gancho useDraft
em outros componentes! É, obviamente, um exemplo muito simples, mas a reutilização de código é muito importante e o exemplo mostra como é fácil com Hooks.
Ganchos nos permitem escrever componentes de maneira mais intuitiva
Vamos imaginar um componente de classe que renderize, por exemplo, uma tela de bate-papo, lista de bate-papos e formulário de mensagem. Assim:
class ChatApp extends React.Component { state = { currentChat: null, }; handleSubmit = (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(`Message is sent to chat ${this.state.currentChat}`); }); }; render() { return ( <Fragment> <ChatsList changeChat={currentChat => { this.setState({ currentChat }); }} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={this.handleSubmit} /> </Fragment> ); }; }
Imagine o nosso usuário usando este componente de bate-papo:
- Eles abrem bate-papo 1
- Eles enviam uma mensagem (vamos imaginar uma rede lenta)
- Eles abrem o chat 2
- Eles vêem um alerta sobre a mensagem deles:
- "A mensagem é enviada para o chat 2"
Mas eles enviaram uma mensagem para o segundo bate-papo, como aconteceu? Isso ocorreu porque o método de classe trabalha com o valor atual, não o valor que tínhamos quando estávamos iniciando uma solicitação de mensagem. Não é grande coisa com componentes simples como esse, mas pode ser uma fonte de bugs em sistemas mais complexos.
Por outro lado, os componentes funcionais agem de outra maneira:
const ChatApp = () => { const [currentChat, setCurrentChat] = useState(null); const handleSubmit = useCallback( (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(`Message is sent to chat ${currentChat}`); }); }, [currentChat] ); render() { return ( <Fragment> <ChatsList changeChat={setCurrentChat} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={handleSubmit} /> </Fragment> ); }; }
Vamos imaginar o nosso usuário:
- Eles abrem bate-papo 1
- Eles enviam uma mensagem (vamos imaginar uma rede lenta)
- Eles abrem o chat 2
- Eles vêem um alerta sobre a mensagem deles:
- "A mensagem é enviada para o chat 1"
Bem, o que mudou? Agora estamos trabalhando com um valor, capturado no momento da renderização. Estamos criando um novo handleSubmit
sempre que o currentChat
alterado. Isso nos permite esquecer mudanças futuras e pensar agora .
Cada componente renderiza capturando tudo o que usa .
Ganchos deixam o ciclo de vida dos componentes
Esse motivo se cruza fortemente com o anterior. React é uma biblioteca de interface do usuário declarativa. A declaratividade torna a criação e o processo da interface do usuário muito mais fáceis. Isso nos permite esquecer as alterações imperativas do DOM.
Mesmo assim, quando usamos classes, enfrentamos o ciclo de vida dos componentes. É assim:
- Montagem
- Atualização (sempre que o
state
ou props
alterado) - Desmontando
Parece conveniente, mas eu convenci que é apenas por causa de nossos hábitos. Não é como reagir.
Em vez disso, os componentes funcionais nos permitem escrever o código dos componentes e esquecer o ciclo de vida. Pensamos apenas em sincronização . Escrevemos que a função cria nossa interface do usuário a partir de adereços de entrada e estado interno.
No primeiro useEffect
gancho de efeito parece substituir os componentDidMount
, componentDidUpdate
e outros métodos de ciclo de vida. Mas não é assim. Quando usamos o useEffect
, dissemos para reagir: "Ei, faça isso depois de renderizar meu componente".
Aqui está um bom exemplo do grande artigo sobre useEffect :
- Reagir: me dê a interface do usuário quando o estado for
0
. - Seu componente:
- Aqui está o resultado da renderização:
<p>You clicked 0 times</p>
. - Lembre-se também de executar este efeito depois de terminar:
() => { document.title = 'You clicked 0 times' }
.
- Reagir: Claro. Atualizando a interface do usuário. Ei, navegador, estou adicionando algumas coisas ao DOM.
- Navegador: Legal, eu pintei na tela.
- Reagir: OK, agora vou executar o efeito que você me deu.
- Running
() => { document.title = 'You clicked 0 times' }
.
É muito mais declarativo, não é?
No fechamento
O React Hooks nos permite livrar-se de alguns problemas e facilitar o desenvolvimento. Só precisamos mudar nosso modelo mental. De fato, o componente funcional é uma função da interface do usuário dos adereços. Eles descrevem como tudo deve estar a qualquer momento e nos ajudam a esquecer as mudanças.
Bem, precisamos aprender como usá-lo, mas ei, você escreveu os componentes de uma classe corretamente na primeira vez?