Descubre cómo Fiber reconcilia el árbol, agrupa las actualizaciones y hace que el renderizado sea interrumpible.
Abrir esta lección en KodokonDesde React 16, el motor interno se llama Fiber: cada elemento de tu árbol está representado por un nodo fiber, una unidad de trabajo. React mantiene en realidad dos árboles (una técnica de doble búfer): el árbol current, que refleja lo que hay en pantalla, y el árbol workInProgress, construido durante el renderizado. El trabajo se realiza en dos fases distintas: la fase de renderizado (llamar a tus componentes, calcular las diferencias), que es interrumpible y reproducible, y luego la fase de commit (aplicar las mutaciones al DOM, ejecutar los efectos), que siempre es síncrona e ininterrumpible. La reconciliación se apoya en dos heurísticas que reducen la comparación a un coste lineal: si el tipo de un elemento cambia en una posición dada, React destruye todo el subárbol y lo reconstruye, estado incluido; para las listas, la prop key permite a React emparejar los elementos entre dos renderizados.
function List({ items }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>
<input defaultValue={item.label} />
</li>
))}
</ul>
);
}
// With key={index}, deleting the first item
// would shift the content of the remaining inputs.El segundo mecanismo fundamental: el batching. Cuando se disparan varias actualizaciones de estado dentro del mismo contexto, React las agrupa en un único renderizado. Antes de React 18, esta agrupación solo funcionaba dentro de los manejadores de eventos de React; desde createRoot, es automática en todas partes: temporizadores, promesas, listeners nativos. Por eso también leer el estado justo después de un setState devuelve el valor antiguo: la actualización está programada, no aplicada.
function handleTick() {
setCount((c) => c + 1);
setFlag((f) => !f);
// React 18: a single render, even here.
}
setTimeout(handleTick, 100);
// Before React 18, this timer would trigger
// two successive renders.El renderizado concurrente lleva la lógica más lejos: la fase de renderizado puede ser interrumpida, reanudada, o incluso descartada y reiniciada si llega una actualización más urgente. React ordena las actualizaciones por prioridad (los lanes): una pulsación de tecla es urgente, un filtro de lista marcado con startTransition no lo es. La consecuencia directa: tus funciones de componente deben ser puras, porque React puede llamarlas varias veces sin llegar nunca a hacer commit del resultado. Otro caso límite: un store externo (Redux, Zustand, un singleton casero) leído durante un renderizado interrumpible puede cambiar a mitad del renderizado, produciendo una interfaz inconsistente conocida como tearing. La API useSyncExternalStore resuelve este problema forzando un renderizado síncrono cuando el store cambia.
import { useSyncExternalStore } from 'react';
function useWindowWidth() {
return useSyncExternalStore(
(notify) => {
window.addEventListener('resize', notify);
return () =>
window.removeEventListener('resize', notify);
},
() => window.innerWidth
);
}<div> se convierte en un <span> en la misma posición?createRoot, ¿cuántos renderizados disparan dos llamadas a setState dentro de un setTimeout?