Gerenciando Estado com useState no React

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.

Tags: React useState javascript Hooks Fiber

Publicado em 7-7 06:43