React Fiber: Arquitetura Interna, Reconciliation e Rendering Phases: Do Básico ao Avançado Já leu

React Fiber: Arquitetura Interna, Reconciliation e Rendering Phases React Fiber é a reimplementação do mecanismo de rendering do React, introduzida na versão 16. Antes do Fiber, o React usava uma abordagem de pilha de chamadas que renderizava a árvore de componentes de forma síncrona, bloqueando a thread principal e impedindo que o navegador processasse eventos, animações ou outras tarefas importantes. O Fiber resolve esse problema dividindo o trabalho de rendering em pequenas unidades chamadas de fibers, que podem ser pausadas, retomadas e priorizadas. A motivação central do Fiber é permitir incremental rendering — a capacidade de dividir o trabalho de rendering em várias frames, priorizando tarefas mais urgentes e deixando tarefas menos críticas para depois. Isso resulta em uma experiência de usuário significativamente melhor, especialmente em dispositivos com menor poder computacional ou durante períodos de alta atividade. O Modelo Mental do Fiber O que é um Fiber? Um fiber é um objeto JavaScript que representa uma unidade de trabalho. Cada

React Fiber: Arquitetura Interna, Reconciliation e Rendering Phases

React Fiber é a reimplementação do mecanismo de rendering do React, introduzida na versão 16. Antes do Fiber, o React usava uma abordagem de pilha de chamadas que renderizava a árvore de componentes de forma síncrona, bloqueando a thread principal e impedindo que o navegador processasse eventos, animações ou outras tarefas importantes. O Fiber resolve esse problema dividindo o trabalho de rendering em pequenas unidades chamadas de fibers, que podem ser pausadas, retomadas e priorizadas.

A motivação central do Fiber é permitir incremental rendering — a capacidade de dividir o trabalho de rendering em várias frames, priorizando tarefas mais urgentes e deixando tarefas menos críticas para depois. Isso resulta em uma experiência de usuário significativamente melhor, especialmente em dispositivos com menor poder computacional ou durante períodos de alta atividade.

O Modelo Mental do Fiber

O que é um Fiber?

Um fiber é um objeto JavaScript que representa uma unidade de trabalho. Cada componente na árvore do React tem um fiber correspondente. Um fiber contém informações sobre o componente, seu estado, props, filhos e referências para fibers relacionados. Diferentemente da abordagem anterior baseada em pilha, fibers formam uma estrutura ligada (linked structure) que permite ao React pausar, resumir e descartar trabalho sem perder o estado.

// Estrutura simplificada de um Fiber
const fiber = {
  // Tipo do componente
  type: MyComponent,

  // Props e estado
  props: { name: 'John' },
  state: { count: 0 },

  // Referências para outros fibers
  parent: parentFiber,
  child: childFiber,
  sibling: siblingFiber,

  // Trabalho associado
  effectTag: 'UPDATE', // 'PLACEMENT', 'UPDATE', 'DELETION'
  effects: [],

  // Instância do componente (class components)
  instance: componentInstance,

  // Hooks e estado funcional
  memoizedState: null,

  // Versão anterior do fiber (para comparação)
  alternate: previousVersionFiber
};

A estrutura de linked fibers é fundamental porque permite que React navegue pela árvore sem depender da pilha de chamadas. A referência alternate é particularmente importante — ela aponta para a versão anterior do fiber, permitindo que React compare o estado anterior com o novo durante a reconciliação.

Por que uma Linked Structure?

Quando o React precisava renderizar uma árvore usando recursão (na abordagem anterior), a pilha de chamadas do JavaScript acumulava todas as chamadas recursivas. Se uma renderização demorasse muito (digamos, alguns segundos), nada mais poderia executar na thread principal — nenhum evento seria processado, nenhuma animação seria desenhada. A linked structure elimina essa dependência da pilha e permite que React controle quando pausar e retomar o trabalho.

Fases de Rendering: Render e Commit

React divide o processo de rendering em duas fases distintas: render phase e commit phase. Compreender essa divisão é essencial para entender Fiber.

Render Phase (Fase de Renderização)

A render phase é onde React executa a reconciliação — comparando a nova árvore com a anterior, determinando quais componentes precisam de atualizações e criando uma lista de efeitos a serem executados. Durante essa fase, React pode pausar, descartar ou retomar o trabalho a qualquer momento. É seguro repetir essa fase múltiplas vezes porque nenhuma alteração real é feita ainda.

// Exemplo: componente que pode ter sua renderização pausada/retomada
function Counter() {
  const [count, setCount] = React.useState(0);

  // Este código executa durante a render phase
  console.log('Rendering component with count:', count);

  const expensiveCalculation = () => {
    let result = 0;
    for (let i = 0; i < 1000000000; i++) {
      result += Math.sqrt(i);
    }
    return result;
  };

  // Este cálculo pesado pode ser entrecortado
  const value = expensiveCalculation();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

Durante a render phase, React não atualiza o DOM ou chama efeitos colaterais (como useEffect). Se você tentar acessar document.getElementById() ou modificar referências diretas durante o render, o comportamento será impredizível porque a fase pode ser pausada ou repetida.

Commit Phase (Fase de Commit)

Após a render phase estar completa, React entra na commit phase — é aqui que as mudanças são realmente aplicadas ao DOM. Diferentemente da render phase, a commit phase é síncrona e não pode ser interrompida. React precisa garantir que toda a árvore de DOM seja atualizada de forma consistente em uma única operação.

function DataFetcher() {
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    // Este código é executado APÓS a commit phase
    console.log('Commit phase completed, now fetching data');

    fetch('/api/data')
      .then(res => res.json())
      .then(json => {
        setData(json);
        setLoading(false);
      });
  }, []);

  return loading ? <p>Loading...</p> : <pre>{JSON.stringify(data)}</pre>;
}

A separação entre render e commit phases é o que permite que React priorize trabalho. Se uma atualização urgente chegar (como resposta a um clique do usuário), React pode pausar uma renderização de baixa prioridade (como atualizar uma lista grande) e processar o clique primeiro.

Reconciliation: O Algoritmo de Comparação

O Algoritmo Reconciliation

A reconciliação é o processo de comparação entre a árvore anterior (representada pelos fibers alternate) e a nova árvore (a que seria renderizada agora). O objetivo é identificar quais nós sofreram mudanças e precisam ser atualizados no DOM.

O React usa heurísticas inteligentes para tornar a reconciliação eficiente:

  1. Elementos de tipos diferentes produzem árvores diferentes: Se o tipo do elemento muda (ex: <div> para <span>), React descarta a árvore antiga e cria uma nova.

  2. Keys estáveis: Quando renderizando listas, adicionar keys (chaves) ajuda React a identificar qual item é qual, mesmo que sua posição mude.

  3. Heurística de profundidade: React não compara fibers em profundidades diferentes — compara apenas fibers no mesmo nível.

function TodoList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        // SEM key: React pode confundir qual item é qual ao reordenar
        // <li key={item.id}> — COM key: React identifica corretamente
        <li key={item.id}>
          {item.text}
        </li>
      ))}
    </ul>
  );
}

// ❌ RUIM: Sem key, React reutiliza fibers baseado em posição
function BadExample() {
  const [items, setItems] = React.useState(['A', 'B', 'C']);

  return (
    <div>
      {items.map((item, index) => (
        <input key={index} defaultValue={item} />
      ))}
      <button onClick={() => setItems(['X', ...items])}>
        Adicionar X
      </button>
    </div>
  );
}

// ✅ BOM: Com key estável, React rastreia corretamente
function GoodExample() {
  const [items, setItems] = React.useState([
    { id: 1, text: 'A' },
    { id: 2, text: 'B' },
    { id: 3, text: 'C' }
  ]);

  return (
    <div>
      {items.map((item) => (
        <input key={item.id} defaultValue={item.text} />
      ))}
      <button onClick={() => setItems([
        { id: Date.now(), text: 'X' },
        ...items
      ])}>
        Adicionar X
      </button>
    </div>
  );
}

Comparação de Fibers

Quando React entra na render phase, ele compara cada fiber com seu alternate (versão anterior). Se não há alternate, é uma nova inserção. Se há alternate mas as props ou estado mudaram, é uma atualização. Se há alternate mas o tipo mudou, é uma exclusão seguida de uma inserção.

// Demonstração de como React rastreia mudanças
function Profile({ userId, showBio }) {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    // Renderização é pausável aqui
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(setUser);
  }, [userId]);

  if (!user) return <p>Loading...</p>;

  return (
    <div>
      <h1>{user.name}</h1>
      {/* Mudanças nas props (showBio) causam re-rendering */}
      {showBio && <p>{user.bio}</p>}
      {/* React compara esta props com a versão anterior */}
      <img src={user.avatar} alt={user.name} />
    </div>
  );
}

// Quando showBio muda de true para false:
// 1. Render phase: React determina que <p> precisa ser removido
// 2. Commit phase: O <p> é removido do DOM
// Tudo sem re-renderizar o componente inteiro

Priorização e Scheduling: Como React Organiza o Trabalho

Níveis de Prioridade

React classifica atualizações em diferentes níveis de prioridade. Atualizações de cliques de usuários têm prioridade alta, enquanto atualizações de dados em background têm prioridade baixa. O scheduler do React observa se há tempo disponível na thread principal e processa trabalhos de baixa prioridade apenas quando não há trabalho de alta prioridade pendente.

// Exemplo usando startTransition (React 18+)
function SearchUsers() {
  const [query, setQuery] = React.useState('');
  const [results, setResults] = React.useState([]);
  const [isPending, setIsPending] = React.useState(false);

  const handleSearch = (e) => {
    const value = e.target.value;

    // Atualização urgente: atualizar o input imediatamente
    setQuery(value);

    // Atualização não urgente: buscar e atualizar resultados
    // Pode ser interrompida se o usuário digitar novamente
    React.startTransition(() => {
      // Simulação de busca pesada
      const filtered = largeDataset.filter(user =>
        user.name.includes(value)
      );
      setResults(filtered);
    });
  };

  return (
    <div>
      <input 
        value={query}
        onChange={handleSearch}
        placeholder="Search..."
      />
      {isPending && <p>Searching...</p>}
      <ul>
        {results.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

Nesse exemplo, a atualização do query é processada imediatamente (alta prioridade), enquanto a filtragem dos results é uma transição (baixa prioridade) e pode ser interrompida. Se o usuário digitar outra letra, React descarta o trabalho anterior de filtragem e começa novamente com a nova query — sem bloquear o input.

Time Slicing

Time slicing é a técnica que permite que React interrompa o trabalho de render phase em pequenos intervalos, cedendo tempo para o navegador executar outras tarefas. React usa requestIdleCallback (ou um polyfill) para detectar quando há tempo disponível na thread principal.

// Este componente renderiza uma lista grande
function LargeList({ items }) {
  console.time('render');

  // Mesmo com muitos itens, a renderização não bloqueia a UI
  // porque React a divide em chunks usando time slicing
  const renderedItems = items.map(item => (
    <div key={item.id} style={{ padding: '10px', border: '1px solid gray' }}>
      <h3>{item.title}</h3>
      <p>{item.description}</p>
      {/* React pode pausar aqui para processar eventos */}
    </div>
  ));

  console.timeEnd('render');

  return <div>{renderedItems}</div>;
}

// Uso com 10.000 itens não trava a UI
function App() {
  const items = Array.from({ length: 10000 }, (_, i) => ({
    id: i,
    title: `Item ${i}`,
    description: `Description for item ${i}`
  }));

  return <LargeList items={items} />;
}

React não renderiza todos os 10.000 itens de uma vez. Em vez disso, ele renderiza alguns, cede controle para o navegador processar eventos ou pintar na tela, depois volta e renderiza mais alguns.

Conclusão

Fiber é a arquitetura que tornou React verdadeiramente responsivo e capaz de lidar com interfaces complexas sem travar. Os três pilares para dominar Fiber são: (1) entender que fibers são objetos JavaScript que formam uma linked structure, permitindo que React controle quando pausar e retomar o trabalho; (2) separar mentalmente render phase (pausável, sem efeitos colaterais) de commit phase (síncrona, onde mudanças são aplicadas), e (3) reconhecer que reconciliation é um algoritmo de comparação inteligente que usa heurísticas como keys estáveis e tipos de elementos para minimizar trabalho desnecessário.

Com esse conhecimento, você entenderá por que certas práticas (como usar index como key) prejudicam performance, e poderá escrever código React mais eficiente e previsível.

Referências


Artigos relacionados