A partir da versão 16.8 do React, uma nova forma de trabalhar com a reatividade dos componentes foi introduzida: React Hooks.
Essa é a maior alteração na forma de trabalhar do React desde sua concepção. Por isso, neste artigo, vamos constatar as motivações da mudança na forma de trabalhar e quais suas vantagens.
Neste artigo serão abordados os seguintes tópicos:
Tempo de Leitura: 8 Minutos.
É importante deixar claro que NÃO é obrigatório o uso de hooks nas novas versões do React, assim como o antigo modo de trabalhar, utilizando classes, não teve seu suporte removido.
Até então, sempre trabalhamos com dois tipos de componentes: Os stateful e os stateless. A diferença conceitual entre eles é bem simples, mas a implementação se diverge bastante.
Vamos supor que tenhamos um componente funcional (stateless) e que por motivos de negócio, precisamos transformá-lo em um componente stateful. No caso de uma lista de produtos hipotética, teríamos este cenário:
import React from "react";
function ProductList(props) {
return (
<ul>
{props.products.map((product) => (
<span key={product.id}>{product.name}</span>
))}
</ul>
);
}
Mas se quisermos permitir a edição de cada um dos produtos, precisaremos transformar totalmente este componente, muito além de apenas substituir o span por um input:
class ProductList extends React.Component {
constructor(props) {
super(props);
this.state = { products: [...props.products] };
}
onChangeHandler(event, id) {
const updatedProducts = this.state.products.map((product) => {
if (product.id === id) {
product.name = event.target.value;
}
return product;
});
this.setState({
products: updatedProducts,
});
}
render() {
return (
<ul>
{this.state.products.map((product) => (
<input
type="text"
key={product.id}
value={product.name}
onChange={(event) => this.onChangeHandler(event, product.id)}
/>
))}
</ul>
);
}
}
Notamos aqui que para termos um estado ligado ao componente, precisamos abandonar o componente escrito de forma funcional e escrevê-lo em forma de classe, trabalhar com as particularidades do React, estendendo a classe React. Component, invocando super em seu construtor e criando o método render. Bastante trabalho.
De acordo com a própria documentação do React, existem 3 pontos principais que levaram à criação da API de Hooks:
Não existia uma forma de se compartilhar lógica entre os estados.
O que se se fazia era criar um HOC (High-order component) para ter esse comportamento “reutilizável” em múltiplos pontos da aplicação, que ainda assim não resolvia o problema, uma vez que para se utilizar o HOC, os componentes que viriam a trabalhar com ele precisariam receber modificações, fazendo com que deixar de usá-los com HOC em alguns casos se tornasse uma tarefa um tanto complexa.
Por conta da natureza dos componentes stateful do React, é comum a lógica ficar separada em diferentes ciclos de vida, e conforme o componente cresce, cria-se uma complexidade cognitiva. Este é um problema não só do ponto de vista estrutural, mas uma grande dificuldade no que diz respeito à testabilidade do código, já que a lógica está intrínseca ao ciclo de vida.
Com os hooks, podemos criar uma divisão da lógica baseada no negócio, e não nos ciclos de vida.
Aqui a documentação toca em 2 pontos: Classes confundem pessoas e Classes confundem máquinas.
Eu, particularmente, possuo uma opinião diferente sobre o primeiro ponto.
Por mais que utilizar classes em JavaScript necessite do aprendizado de como o this funciona na linguagem, a necessidade de realizar bind nos métodos da classe, a necessidade de alternar entre componentes de classe e funcionais para utilizá-los de forma stateless e stateful e outras complexidades que existe em utilizar o React com classes (conforme vimos em exemplo acima) foram criadas pela própria equipe do React.
As regrinhas que precisam ser seguidas são fruto da lib, e não da linguagem. Por isso a criação da API de Hooks.
Sobre o segundo ponto, os problemas relatados por eles, como problemas em minificação (no momento do bundling) e problemas com hot reloading, podem acontecer, mas não são tão frequentes.
Vamos refatorar nosso componentes class-based para utilizar a nova API de Hooks. Os Hooks se dividem em diferentes tipos, onde os mais comuns de ser utilizados são:
Neste primeiro artigo, iremos abordar um dos mais utilizados, que é o useState.
Este Hook vem para substituir exatamente a necessidade de criar um componente class-based quando queremos apenas ter um estado relacionado ao componente.Refatorando nosso componente ProductList, chegamos neste resultado:
export default function ProductList(props) {
const [products, setProducts] = useState(props.products);
const onChangeHandler = (event, id) => {
const updatedProducts = products.map((product) => {
if (product.id === id) {
product.name = event.target.value;
}
return product;
});
setProducts(updatedProducts);
};
return (
<ul>
<ul>
{products.map((product) => (
<input
type="text"
key={product.id}
value={product.name}
onChange={(event) => onChangeHandler(event, product.id)}
/>
))}
</ul>
</ul>
);
}
Vamos entender o que está acontecendo aqui. Podemos notar os seguintes pontos:
Dos pontos acima, o que é mais interessante discutirmos é como o useState funciona.
const [products, setProducts] = useState(props.products);
Quando invocamos o hook, podemos passar um valor para a invocação. No nosso caso, estamos passando a lista de produtos que recebemos como props. Em alguns casos, poderia ser um array vazio, ou uma string com um valor default, etc.
O valor informado na hora da invocação é o valor default do estado. Ou seja, o valor com o qual é iniciado.
Podemos já relacionar com o funcionamento do state em class-based components:
Ou seja, a forma de trabalhar é um pouco diferente. Enquanto num componente class-based teríamos apenas um this.state com todos os dados do state nele, com hooks nós teremos múltiplas chamadas para o useState, uma para cada chave de estado. Por exemplo:
const [products, setProducts] = useState(props.products);
const [username, setUsername] = useState("bruno.fachine");
const [selectedProducts, setSelectedProducts] = useState([]);
O retorno da invocação é um array com 2 posições ocupadas:
O uso fica bem claro no método onChangeHandler, onde utilizamos tanto o getter quanto o setter:
const onChangeHandler = (event, id) => {
const updatedProducts = products.map((product) => {
if (product.id === id) {
product.name = event.target.value;
}
return product;
});
setProducts(updatedProducts);
};
Esse é um post introdutório sobre react hooks. Iremos aprofundar os conceitos e detalhar os outros hooks que estão disponíveis. No próximo post falaremos sobre o hook useEffect, que substitui o ciclo de vida de um componente class-based.
Até a próxima!