No React, o fluxo de dados é unidirecional, descendo do componente pai para o filho. Os dados em um componente são armazenados em props e state. Enquanto as props são imutáveis e passadas de cima para baixo, o state é uma estrutura de dados local, controlada e mutável apenas pelo próprio componente. Ele é inicializado internamente e serve para gerenciar alterações que impactam a renderização. Em componentes funcionais, o hook useState é a ferrametna padrão para gerenciar esse estado local.
O Conceito de Hooks
Os Hooks do React são funções que permitem "conectar" o estado e outras funcionalidades do React a componentes funcionais. Conforme uma definição popular, eles permitem que componentes puros (funções) utilizem efeitos colaterais e estados externos através de "ganchos". O useState é um exemplo claro: ao chamá-lo dentro de um componente funcional, você está puxando para o escopo da função um mecanismo interno que preserva o estado entre renderizações, utilizando o conceito de clousures.
A motivação principal para a adoção dos Hooks é a reutilização de lógica de forma mais granular e limpa. Diferente de padrões anteriores como Higher-Order Components (HOCs) e Render Props, que frequentemente resultam em uma árvore de componentes complexa ("Wrapper Hell") e dificultam a refatoração, os Hooks permitem extrair lógica com estado para funções independentes. Isso resolve vários problemas:
- Dificuldade em reutilizar lógica com estado: HOCs e Render Props exigem a reestruturação dos componentes. Hooks permitem extrair e reutilizar lógica sem alterar a hierarquia do componente.
- Complexidade em componentes grandes: Em componentes classe, lógicas não relacionadas (como chamadas de API e listeners) são misturadas nos métodos do ciclo de vida. Hooks permitem agrupar lógica relacionada em funções menores e focadas.
- Dificuldades com classes: O uso de
this, a necessidade de bind de métodos e a verbosidade tornam as classes menos intuitivas. Hooks permitem usar todo o poder do React com funções, que são mais simples e previsíveis.
Como o useState Funciona Internamente
Vamos analisar o exemplo mais básico de useState:
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const addCount = () => setCount(count + 1);
return (
<>
<div>{count}</div>
<button onClick={addCount}>Count++</button>
</>
);
}
Toda vez que setCount é chamado, o componente App() é executado novamente. Se a linha const [count, setCount] = useState(0); é executada a cada nova renderização, como o React garante que count não seja reinicializado para 0 a cada vez?
A resposta está em como o React gerencia o estado internamente. Vamos construir uma versão simplificada para entender o mecanismo.
Considere uma implementação inicial usando uma variável global para armazenar o estado:
let saveState = null;
function useMyState(initial) {
saveState = saveState !== null ? saveState : initial;
const setState = (newState) => {
saveState = newState;
forceRender(); // Função hipotética para renderizar novamente
};
return [saveState, setState];
}
Essa abordagem falha com múltiplos estados, pois todos compartilhariam a mesma variável saveState.
A solução adotada (e similar ao que o React faz) é usar uma estrutura de dados como um array ou uma lista encadeada. A cada chamada de useState, um novo slot é adicionado a essa estrutura, e a ordem das chamadas é crucial. Veja uma versão com array:
let states = [];
let cursor = 0;
function useMyState(initial) {
const currentIndex = cursor;
cursor++;
states[currentIndex] = states[currentIndex] !== undefined ? states[currentIndex] : initial;
const setState = (newState) => {
states[currentIndex] = newState;
cursor = 0; // Reinicia o cursor para a próxima renderização
forceRender();
};
return [states[currentIndex], setState];
}
Nesta versão, cada chamada a useMyState pega um índice único. Quando o estado é atualizado, o cursor é zerado para que a nova renderização possa percorrer os estados na mesma ordem. Isso demonstra por que a ordem das chamadas de hooks não pode mudar entre renderizações (por exemplo, dentro de condicionais).
Exemplo inválido:
const App = () => {
let state;
if (someCondition) {
[state, setState] = useState(0); // ERRO: Ordem dos hooks pode variar
}
// ...
};
No React real, em vez de um array simples, a estrutura é uma lista encadeada de objetos Hook. Cada nó dessa lista (chamado de memoizedState no FiberNode) contém o estado atual, a fila de atualizações pendentes e um ponteiro para o próximo hook. A implementação real é mais complexa, mas o princípio fundamental é o mesmo: a ordem de declaração dos hooks é usada como chave para associar cada chamada de useState ao seu estado correspondente entre renderizações.