Dominando React Internals: Reconciler, Fiber Architecture e Rendering Phases em Projetos Reais Já leu

Entendendo o Reconciler: O Coração do React O Reconciler é o mecanismo que React utiliza para determinar quais mudanças devem ser aplicadas ao DOM. Quando você atualiza o estado de um componente, o React não simplesmente reescreve tudo na tela. Em vez disso, ele compara a árvore anterior (virtual) com a nova, identificando apenas as diferenças. Esse processo é chamado de reconciliação ou diffing. Antes do React Fiber (versão 16+), o algoritmo de reconciliação era síncrono e bloqueante — se você tivesse uma árvore grande de componentes, o navegador congelava enquanto React calculava as mudanças. O React Fiber resolveu isso introduzindo unidades de trabalho pequenas e preemptíveis, permitindo que o navegador interrompa o trabalho para tarefas de alta prioridade como eventos de usuário ou animações. Fiber Architecture: A Revolução Estrutural A arquitetura Fiber decompõe o trabalho em unidades chamadas fibers. Cada fiber representa um componente, um hook, um texto ou qualquer outro elemento na árvore. Um fiber é um objeto

Entendendo o Reconciler: O Coração do React

O Reconciler é o mecanismo que React utiliza para determinar quais mudanças devem ser aplicadas ao DOM. Quando você atualiza o estado de um componente, o React não simplesmente reescreve tudo na tela. Em vez disso, ele compara a árvore anterior (virtual) com a nova, identificando apenas as diferenças. Esse processo é chamado de reconciliação ou diffing.

Antes do React Fiber (versão 16+), o algoritmo de reconciliação era síncrono e bloqueante — se você tivesse uma árvore grande de componentes, o navegador congelava enquanto React calculava as mudanças. O React Fiber resolveu isso introduzindo unidades de trabalho pequenas e preemptíveis, permitindo que o navegador interrompa o trabalho para tarefas de alta prioridade como eventos de usuário ou animações.

// Exemplo simples: como o React faz diffing
function reconcile(oldVNode, newVNode) {
  // Se tipos são diferentes, substitui toda a árvore
  if (typeof oldVNode !== typeof newVNode) {
    return newVNode;
  }

  // Se ambos são strings ou números, compara direto
  if (typeof newVNode !== 'object') {
    return oldVNode === newVNode ? oldVNode : newVNode;
  }

  // Se são objetos (componentes), compara props e filhos
  if (oldVNode.type === newVNode.type) {
    return {
      ...newVNode,
      children: reconcileChildren(oldVNode.children, newVNode.children)
    };
  }

  return newVNode;
}

Fiber Architecture: A Revolução Estrutural

A arquitetura Fiber decompõe o trabalho em unidades chamadas fibers. Cada fiber representa um componente, um hook, um texto ou qualquer outro elemento na árvore. Um fiber é um objeto JavaScript com referências para seu pai (return), seus filhos (child) e seu irmão (sibling), formando uma estrutura de árvore ligada.

O grande ganho é a interruptibilidade: React pode pausar o trabalho em um fiber, salvar seu estado, permitir que o navegador execute tarefas mais urgentes e depois retomar de onde parou. Isso é possível porque o trabalho foi dividido em pequenas unidades que cabem em um time slice (janela de tempo do navegador, geralmente 5ms).

// Estrutura simplificada de um Fiber
const fiber = {
  type: MyComponent,           // Tipo do componente
  key: 'unique-key',           // Key para reconciliação
  props: { name: 'João' },     // Props do componente
  state: null,                 // Estado local (hooks)

  // Relações na árvore
  return: parentFiber,         // Referência ao pai
  child: childFiber,           // Primeiro filho
  sibling: siblingFiber,       // Próximo irmão

  // Rastreamento de mudanças
  alternate: oldFiber,         // Versão anterior para diff
  effectTag: 'UPDATE',         // UPDATE, PLACEMENT, DELETION
  effects: [],                 // Effects para executar
};

// Exemplo de como React percorre a árvore de Fibers
function performUnitOfWork(fiber) {
  // 1. Processa o próprio fiber
  processComponent(fiber);

  // 2. Se tem filho, trabalha nele primeiro (depth-first)
  if (fiber.child) {
    return fiber.child;
  }

  // 3. Se não tem filho, trata efeitos e volta
  completeUnitOfWork(fiber);

  // 4. Tenta o irmão
  if (fiber.sibling) {
    return fiber.sibling;
  }

  // 5. Volta para o pai
  return fiber.return;
}

Rendering Phases: Render e Commit

O React dividiu o processo de renderização em duas fases distintas: Render Phase e Commit Phase. Essa separação é crucial para entender como React implementa concorrência e por que certas otimizações funcionam.

Render Phase (Assíncrona e Preemptível)

Na fase de render, React constrói a nova árvore de Fibers, executa componentes, calcula diffs e marca qual trabalho precisa ser feito. Nada é aplicado ao DOM ainda. Como essa fase é preemptível, React pode pausar, abandonar e recomeçar sem problemas. É por isso que efeitos colaterais nunca devem estar no corpo do componente — o componente pode ser renderizado múltiplas vezes sem sucesso.

Commit Phase (Síncrona e Irrevogável)

Na fase de commit, React aplica todas as mudanças de uma vez ao DOM. Essa fase é síncrona porque queremos que o DOM chegue a um estado consistente rapidamente. Aqui também são executados os hooks como useEffect e useLayoutEffect.

// Simulação das duas fases do React
let workInProgressRoot = null;

function scheduleRender(component, container) {
  workInProgressRoot = createFiber(component);

  // Inicia o render phase (pode ser interrompido)
  scheduleCallback(performRenderPhase);
}

function performRenderPhase() {
  // React constrói a árvore de Fibers
  let nextUnitOfWork = workInProgressRoot;

  while (nextUnitOfWork && shouldYield()) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
  }

  // Se terminou (shouldYield() retorna false), vai pro commit
  if (!nextUnitOfWork) {
    performCommitPhase(workInProgressRoot);
  } else {
    // Ainda tem trabalho, reagenda
    scheduleCallback(performRenderPhase);
  }
}

function performCommitPhase(root) {
  // Sincronamente aplica todas as mudanças
  let fiber = root.child;

  while (fiber) {
    // Aplica DOM updates
    if (fiber.effectTag === 'PLACEMENT') {
      const parentDom = fiber.parent.dom;
      parentDom.appendChild(fiber.dom);
    } else if (fiber.effectTag === 'UPDATE') {
      updateDomProperties(fiber.dom, fiber.props, fiber.alternate.props);
    } else if (fiber.effectTag === 'DELETION') {
      fiber.parent.dom.removeChild(fiber.dom);
    }

    // Executa hooks e callbacks
    if (fiber.effectTag !== 'DELETION') {
      fiber.hooks?.forEach(hook => {
        if (hook.type === 'effect') hook.cleanup?.();
        hook.effect?.();
      });
    }

    fiber = getNextFiber(fiber);
  }
}

// Exemplo: por que efeitos colaterais no corpo quebram
function BadComponent() {
  // ❌ NÃO FAÇA ISSO - pode executar múltiplas vezes
  fetch('/api/data').then(/* ... */);

  return <div>Conteúdo</div>;
}

function GoodComponent() {
  // ✅ FAÇA ASSIM - executa apenas após commit
  useEffect(() => {
    fetch('/api/data').then(/* ... */);
  }, []);

  return <div>Conteúdo</div>;
}

Prioridades e Concurrent Rendering

React não trata todos os updates com a mesma urgência. Eventos de clique têm prioridade sobre atualizações de dados de fundo. O React classifica o trabalho em diferentes níveis: ImmediatePriority, UserBlockingPriority, NormalPriority, LowPriority e IdlePriority.

Quando múltiplos updates estão em fila, React trabalha primeiro nos de alta prioridade. Se um update de alta prioridade chega enquanto um de baixa prioridade está sendo renderizado, React descarta o trabalho de baixa prioridade e recomeça com o novo update. Essa decisão é feita na Render Phase justamente porque nada foi commitado ainda.

// Exemplo simplificado de priorização
const priorities = {
  Immediate: 1,
  UserBlocking: 2,
  Normal: 3,
  Low: 4,
  Idle: 5
};

function scheduleUpdate(update, priority) {
  const task = { update, priority, expirationTime: now() + getTimeout(priority) };

  // Insere na fila ordenada por prioridade
  updateQueue.push(task);
  updateQueue.sort((a, b) => a.priority - b.priority);

  // Trata updates de alta prioridade antes
  const highPriorityTask = updateQueue[0];
  processUpdate(highPriorityTask);
}

Conclusão

Entender React Internals é fundamental para escrever código performático. O Reconciler usa diffing para identificar mudanças; a Fiber Architecture permite que React interrompa trabalhos longos em unidades pequenas; e a divisão entre Render e Commit Phases garantem que o DOM chegue a estados consistentes. Combine isso com prioridades inteligentes e você tem um framework capaz de manter 60fps mesmo em aplicações complexas. A próxima vez que escrever um componente, lembre-se: efeitos colaterais vão no useEffect, não no corpo da função.

Referências


Artigos relacionados