Saltar al contenido

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:

  1. Mutación. Aplica al host sólo lo que la fase render marcó con flags: inserciones, cambios de props, borrados.
  2. Intercambio. root.current = finishedWork. El árbol nuevo pasa a ser el actual.
  3. 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 / 96
Traza
Cadena de hooks · <>

Sin 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?