Render y commit: las dos fases
¿Renderizar es lo mismo que actualizar la pantalla?
Al terminar vas a poder explicar
- Separar 'renderizar' de 'pintar' con precisión
- Explicar por qué la fase render tiene que ser pura
- Identificar qué trabajo ocurre en cada fase mirando una traza
«Este componente se renderiza de más» es la frase más repetida y peor entendida de React. Renderizar no es pintar. Son dos cosas distintas, con costos distintos, en momentos distintos.
React divide su trabajo en dos fases con reglas opuestas.
Fase render: en privado y en borrador
React recorre el árbol construyendo el workInProgress. Baja con beginWork y sube con completeWork. En el camino ejecuta la función de cada componente que lo necesite y compara el resultado contra lo anterior.
Esta fase tiene tres propiedades que definen todo lo demás:
- Es invisible. Nada de lo que pasa acá toca la pantalla.
- Es interrumpible. Si llega algo más urgente, React abandona y arranca de nuevo.
- Tiene que ser pura. Como se puede ejecutar dos veces o descartar a la mitad, cualquier efecto secundario acá es un bug esperando su turno.
Fase commit: en público y de una
Cuando el workInProgress está completo, React aplica los cambios. Esta fase es lo contrario de la anterior: no se puede interrumpir. Corre entera, de un saque, porque una UI a medio actualizar sería peor que una UI vieja.
El commit hace tres cosas, en este orden:
- Mutación. Aplica al host sólo lo que la fase render marcó con flags: inserciones, cambios de props, borrados.
- Intercambio.
root.current = finishedWork. El árbol nuevo pasa a ser el actual. - Efectos de layout. Corren los
useLayoutEffect, todavía antes de que el navegador pinte.
Recién después de todo eso el navegador pinta. Y recién después de pintar corren los useEffect.
La línea de tiempo completa: agendado → render → commit → pintado → efectos
AGENDADOpaso 1 / 96Sin hooks en este paso.
Árbol de fibers
- <HostRoot>
Salida
Nada en el host todavía. React no commiteó ningún cambio.
createRoot().render()
Montaje inicial. No hay árbol previo, así que todo se va a crear desde cero.
VER EL CÓDIGO QUE SE ESTÁ TRAZANDO
1function Contador() {2 const [n, setN] = useState(0);3 4 return (5 <div>6 <p>Valor: {n}</p>7 <button onClick={() => setN(n + 1)}>sumar 1</button>8 </div>9 );10}Con el foco acá: ← → un paso · espacio reproduce · Inicio/Fin van a los extremos.
Poné la traza en «sólo hitos» y reproducila. Vas a ver la secuencia limpia, sin el detalle de cada fiber. El paso 🖌 el navegador pinta marca el único instante en que el usuario ve algo. Todo lo anterior fue trabajo invisible.
Mirá también el panel Salida durante la fase render: dice que el host todavía está vacío. Eso no es un error del trazador. Es literal: durante el render no hay nada en pantalla todavía.
Por qué la pureza no es una recomendación
Si la fase render se puede descartar y volver a ejecutar, un console.log puede aparecer dos veces, un contador que incrementás en el cuerpo del componente puede saltar de a dos, y un fetch puede dispararse de más.
Por eso React en desarrollo, con StrictMode, llama tu función dos veces y descarta un resultado. No es un bug ni un desperdicio: es un detector de impurezas. Si tu componente se comporta distinto la segunda vez, tenés un efecto secundario donde no va.
Antes de seguir · predecí
Un componente se renderiza pero React no toca ni un nodo del DOM. ¿Qué pasó?
Lo que te llevás
Render construye en privado y se puede interrumpir. Commit publica y no se puede interrumpir. Pintar viene después del commit, y los useEffect después de pintar. Cuando alguien diga «esto renderiza de más», ahora podés preguntar la pregunta correcta: ¿y llegó a tocar el DOM?
Antes de marcarla, comprobá
- ¿Podés explicar separar 'renderizar' de 'pintar' con precisión?
- ¿Podés explicar explicar por qué la fase render tiene que ser pura?
- ¿Podés explicar identificar qué trabajo ocurre en cada fase mirando una traza?