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
- React Fiber Architecture - Lin Clark (Código Confex)
- React Reconciliation - Official Documentation
- The How and Why on React's Usage of Linked List in Fiber to Walk the Component Tree
- React Fiber Deep Dive - Dan Abramov's Blog
- Inside Fiber: an in-depth overview of the new reconciliation engine in React