Cómo dibuja el navegador y cuál es el papel de React en ese proceso.
Aprende cómo el Critical Rendering Path, la reconciliación, Fiber y el Scheduler trabajan juntos para convertir las actualizaciones de React en píxeles en la pantalla.
Primero, olvídense de React: ¿cómo dibuja realmente un navegador una página?
Dejen React a un lado por un momento. Incluso un documento HTML básico con algo de CSS sigue una secuencia fija de pasos antes de que aparezca un solo píxel. Cada navegador sigue esta secuencia, sin importar qué herramientas hayan creado la página:
HTML → DOM tree
CSS → CSSOM tree
DOM + CSSOM → Render Tree
Render Tree → Layout → Paint → Composite → Screen
Cada etapa realiza una tarea específica, y los nombres por sí solos no lo dejan tan claro:
- Árbol DOM — el navegador analiza su HTML y lo convierte en un árbol de nodos. Se trata únicamente de estructura: qué elementos se encuentran dentro de otros.
- Árbol CSSOM — la misma lógica se aplica a los estilos. Cada regla CSS que escriba se convierte en un árbol al que el navegador puede acceder.
display: none sigue existiendo en el DOM pero queda excluido del árbol de renderizado).Juntos, estos pasos forman lo que se conoce como el Critical Rendering Path, o CRP.
Esta secuencia se ejecuta de manera idéntica ya sea que utilice React, Vue, jQuery puro o ninguna biblioteca en absoluto. Dibujar los píxeles es responsabilidad del navegador, no algo que asuma algún framework de interfaz.
¿Entonces, dónde encaja React en todo esto?
Esta es la parte que requiere un momento para ser procesada al hacer clic. React no reemplaza el Critical Rendering Path; opera antes que él.
Sin React, actualizar la interfaz significa localizar manualmente el nodo DOM correcto y modificarlo uno mismo:
const counterEl = document.getElementById('counter');
counterEl.textContent = newCount;
Eso es manejable para un contador simple. Pero imagine un panel de control donde cuarenta valores separados pueden cambiar de forma independiente: tendría que rastrear y actualizar cada uno manualmente. Resolver exactamente ese problema es la razón de existir de React.
Con React, la misma actualización se ve de esta manera en su lugar:
function Counter({ count }) {
return <p>{count}</p>;
}
Se describe cómo debería verse la interfaz teniendo en cuenta los datos actuales, y nunca se modifica directamente el DOM. Por lo tanto, algo más tiene que realizar ese trabajo. Esa es la función real de React, y ocurre como un paso antes incluso de que comience la ruta crítica de renderizado del navegador:
State changes → React figures out what changed → applies a small patch to the real DOM
↓
browser does its normal thing: Layout → Paint → Composite → Screen
Toda la propuesta de valor de React se resume en hacer ese primer paso —determinar qué ha cambiado— lo más rápido y preciso posible, de modo que el navegador solo tenga que volver a procesar el diseño y la pintura de esa pequeña parte de la página que realmente lo necesita, en lugar de volver a procesar todo.
Imperativo versus declarativo: el cambio de enfoque
Este contraste explica por qué React fue diseñado de la forma en que lo es.
El código imperativo detalla cada paso individual:
list.innerHTML = '';
for (const item of items) {
const li = document.createElement('li');
li.textContent = item;
list.appendChild(li);
}
Tú eres quien decide: bórralo, crea ese elemento y átalo aquí.
El código declarativo, en cambio, describe el resultado que deseas ver:
<ul>
{items.map(item => <li key={item}>{item}</li>)}
</ul>
En lugar de instruir al navegador para que “cree un li e lo inserte”, se indica “dado este array, así debería ser la interfaz de usuario resultante”. Algo más tiene que convertir esa descripción en operaciones concretas del DOM; entender qué es ese algo es lo siguiente que se debe hacer.
Qué significa realmente la “reconciliación”
Esto parece más complicado de lo que realmente es una vez que se examina directamente.
React mantiene una instantánea mental de tu interfaz de usuario conocida como Virtual DOM. Cada vez que algo cambia, React construye una versión nueva de ese árbol y la compara con la anterior para determinar qué es lo que ha cambiado. Ese paso de comparación es lo que la gente denomina reconciliación.
Verificar todas las diferencias posibles entre dos árboles sería computacionalmente muy costoso, por lo que React utiliza un atajo con dos reglas que mantienen el rendimiento en situaciones reales:
- El tipo de elemento cambió (por ejemplo, un `
` se convierte en ``) — React ni siquiera examina los hijos; descarta por completo el nodo antiguo y crea uno nuevo.
- El tipo de elemento permaneció igual (`
se convierte en
`) — React mantiene el nodo del DOM real existente y solo modifica las partes que difieren.
Una trampa que afecta a casi todos tiene que ver con las listas. Por defecto, React compara los elementos de la lista por su índice: el elemento 0 con el elemento 0, el elemento 1 con el elemento 1, y así sucesivamente. Si insertas un nuevo elemento al principio de una lista sin clave, React asume que todos los elementos siguientes también han cambiado.
Esa es la razón por la cual siempre debes asignar una clave estable (key) a los elementos de la lista:
{items.map(item => <li key={item.id}>{item.name}</li>)}
Una vez que los elementos tienen una clave, React puede reconocer “este elemento en particular acaba de cambiar de posición” en lugar de asumir que toda la lista se ha reconstruido.
Después de todas estas comparaciones, React termina con un conjunto breve y específico de instrucciones, como “actualizar este nodo de texto” o “insertar un nodo aquí”, y son estas operaciones las que realmente se aplican al DOM real.
¿No es esto lo mismo que hace Fiber?
Es una pregunta legítima, y una que merece ser analizada con detenimiento.
La reconciliación es la idea fundamental. Fiber es simplemente el mecanismo que la pone en práctica.
Antes de React 16, ese mecanismo se denominaba Stack Reconciler. Recorría todo el árbol de forma recursiva y sincrónica, lo que significaba que, una vez iniciado, tenía que completarse antes de detenerse. En el caso de actualizaciones grandes, esto podía ocupar el hilo principal durante tanto tiempo que la aplicación se volvía lenta: los fotogramas desaparecían y la escritura parecía no responder.
Fiber, introducido en React 16, reemplazó a ese mecanismo. El concepto de reconciliación no cambió, pero ahora el trabajo se divide en pequeños fragmentos que pueden pausarse, descartarse o retomarse más tarde. Si surge algo más urgente — como que el usuario escribe — React puede interrumpir cualquier tarea de menor prioridad que estuviera realizando, atender la actualización urgente y luego volver al punto donde lo dejó.
Por lo tanto, no es preciso decir que Fiber reemplazó a la reconciliación. Es más correcto afirmar que el motor anterior que realizaba la reconciliación fue sustituido por uno más capaz.
¿Y qué hace el Programador de tareas?
Fiber hace posible pausar y reanudar el trabajo, pero es necesario que otra cosa decida cuándo pausar y qué tarea merece prioridad. Esa es la función del Programador de tareas.
Sus responsabilidades incluyen:
- Actualizaciones de clasificación según la urgencia: por ejemplo, tratar una tecla presionada en un campo de entrada como algo urgente, mientras que la actualización de una lista de fondo a distancia no lo es.
- Rellenar los espacios entre los marcos del navegador para avanzar de forma incremental en tareas de menor prioridad, y retroceder antes de que sea necesario renderizar el siguiente marco.
- Habilitar las funcionalidades de React 18 como
startTransition; al marcar una actualización como no urgente, se indica efectivamente al programador de tareas que puede posponer dicha tarea en la lista de prioridades.
Un modelo mental simple une estos tres conceptos:
Reconciliation → the algorithm (what changed?)
Fiber → the engine that makes that algorithm interruptible
Scheduler → the traffic controller deciding when to pause/resume Fiber's work
Fase de renderizado versus fase de confirmación
Hay otra distinción que vale la pena entender: Fiber divide su trabajo en dos fases que siguen reglas muy diferentes.
La Fase de Renderizado es donde tiene lugar la comparación real. React invoca las funciones de tu componente, construye el nuevo árbol y lo compara con el anterior. Nada de esto afecta aún a la página real, por lo que esta fase puede detenerse, descartarse o reiniciarse sin problemas.
La Fase de Confirmación es donde React finalmente escribe en el DOM real y aplica la corrección calculada. Esta fase no puede interrumpirse: se ejecuta de principio a fin en una sola pasada continua, ya que una actualización parcial de la interfaz dejaría la página en un estado visual defectuoso. Inmediatamente después de que se actualiza el DOM, pero antes de que el navegador dibuje la pantalla, useLayoutEffect se ejecuta de forma síncrona. En contraste, useEffect se ejecuta un poco más tarde, una vez que el navegador ya ha terminado de dibujar.
¿Realmente se necesita React?
Honestamente, no siempre. Muchos sitios web de producción funcionan únicamente con HTML, CSS y JavaScript puro, y funcionan perfectamente.
React comienza a justificar su sobrecarga una vez que los requisitos se vuelven más complejos:
- Conectar manualmente las actualizaciones del DOM es manejable en proyectos pequeños, pero se vuelve inviable cuando hay que lidiar con docenas de elementos de interfaz interdependientes.
- Una gran parte de los errores en la interfaz de usuario en el mundo real se deben a que el estado y la interfaz mostrada dejan de estar sincronizados. El enfoque de React —tratar la interfaz como una función del estado y dejar que el framework se encargue de las diferencias— elimina gran parte de ese riesgo por diseño.
- Poder crear componentes reutilizables, respaldados por un ecosistema de herramientas de enrutamiento, herramientas de desarrollo y convenciones compartidas, se vuelve valioso cuando más de un desarrollador trabaja en la misma base de código.
No obstante, para una página de aterrizaje sencilla o un sitio en su mayor parte estático, JavaScript puro es la mejor opción. Incorporar Fiber, el Scheduler y todo el pipeline de reconciliación significaría asumir costos adicionales por un problema que en realidad nunca se tuvo.
React no es inherentemente superior a JavaScript. Es una herramienta diseñada para resolver un problema específico: mantener la interfaz de usuario sincronizada con un estado que cambia constantemente, a gran escala, entre varios miembros del equipo. Por debajo de esa escala, HTML, CSS y JS puro manejan la tarea perfectamente bien.
Lecturas relacionadas
- Solucionar la sobrecarga de propiedades en React con composición y slots — Aprenda por qué las propiedades de React con mucha configuración generan deudas de mantenimiento, y cómo la inversión de control, la composición y los slots permiten crear componentes verdaderamente reutilizables.