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:
-
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. -
Keys estáveis: Quando renderizando listas, adicionar keys (chaves) ajuda React a identificar qual item é qual, mesmo que sua posição mude.
-
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.