Lanes: cómo React decide qué hacer primero
Si llegan dos updates a la vez, ¿cuál procesa React primero?
Al terminar vas a poder explicar
- Explicar qué es una lane y por qué es un bitmask
- Describir qué pasa con un update de baja prioridad durante una pasada urgente
- Justificar para qué sirve baseState en la cola de updates
Hasta acá tratamos el trabajo de React como si fuera uno solo: llegan updates, se procesan, se commitea. Pero un update no es igual a otro. Que el usuario esté escribiendo en un campo y que una lista de mil elementos se esté filtrando son cosas con urgencias distintas.
React lo modela con lanes: un bitmask donde cada bit representa una prioridad. Y la regla es simple y brutal: React nunca renderiza «todo lo pendiente». Elige el conjunto de lanes de mayor prioridad y deja el resto para otra pasada.
Por qué un bitmask
Un fiber puede tener varios trabajos pendientes a la vez, de prioridades distintas. Con un entero de 31 bits, preguntar «¿hay trabajo urgente acá abajo?» es un &. Sumar prioridades es un |. Sacar la que ya se procesó es un & ~. Todo en una instrucción.
1const NoLane = 0b000;2const SyncLane = 0b001; // eventos: click, tecla, foco3const DefaultLane = 0b010; // lo normal4const TransitionLane = 0b100; // marcado como "puede esperar"5 6// ¿este fiber tiene trabajo urgente?7if (fiber.lanes & SyncLane) { … }8 9// agregar trabajo10fiber.lanes |= TransitionLane;11 12// sacar lo que ya se renderizó13fiber.lanes &= ~renderLane;Cuando llamás a setState, React marca esa lane subiendo por los punteros return hasta la raíz. Esas marcas son un mapa: al bajar de nuevo, React sabe por qué ramas hay trabajo de esta prioridad y cuáles puede saltearse enteras.
Una pasada por prioridad
Acá está la parte que cambia todo. Si hay un update urgente y uno de transición esperando, React no los junta. Renderiza el urgente, commitea, deja que el navegador pinte — y recién entonces arranca otra pasada completa para la transición.
Un click, dos prioridades: dos pasadas de render y dos pintados
AGENDADOpaso 1 / 223Sin 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 Buscador() {2 const [texto, setTexto] = useState("");3 const [resultados, setResultados] = useState("nada");4 const [pendiente, iniciarTransicion] = useTransition();5 6 function alEscribir(v) {7 setTexto(v); // urgente: la tecla se ve YA8 9 iniciarTransicion(() => { // puede esperar10 setResultados(filtrar(v));11 });12 }13 14 return (15 <div>16 <p>{texto} · {resultados} · {pendiente ? "filtrando…" : "al día"}</p>17 </div>18 );19}Con el foco acá: ← → un paso · espacio reproduce · Inicio/Fin van a los extremos.
Poné la traza en «sólo hitos» y contá los render.begin. Son tres: el montaje, la pasada urgente y la pasada de transición. Y hay tres pintados. El usuario ve el texto del campo actualizado antes de que la lista termine de procesarse.
El update salteado y baseState
¿Qué pasa con el update de transición durante la pasada urgente? Se saltea. Pero saltearlo no es tan simple como ignorarlo, y acá está el detalle que casi nadie conoce.
Buscá en la traza el paso update salteado. Cuando React encuentra un update de menor prioridad, congela el baseState en el valor anterior a ese update y guarda el resto de la cola. En la próxima pasada, todos los updates desde el salteado se vuelven a aplicar, incluso los de prioridad alta que ya se habían aplicado.
1// La cola tiene: +1 (urgente), ×2 (transición), +5 (urgente)2 3// Pasada urgente: aplica +1, saltea ×2, reaplica +5 → 64// baseState congelado en 1 (antes del salteado)5// Pasada transición: desde baseState=1 → ×2 → +5 → 76 7// Si no se reaplicaran, el resultado dependería del orden de8// prioridades en vez del orden en que llamaste a setState.Esa reaplicación es lo que garantiza que el resultado final sea el mismo sin importar cómo React haya repartido el trabajo. El orden en que vos llamaste a los setters manda; el orden de prioridades sólo decide qué se ve antes.
Quién asigna la prioridad
- Eventos discretos — click, tecla, foco — entran con la prioridad más alta. Son la interacción directa del usuario y no pueden esperar.
- Todo lo demás — timeouts, promesas, efectos — entra con prioridad normal.
- Lo que envolvés en
startTransitionentra con prioridad de transición. Es la única que decidís vos explícitamente.
Antes de seguir · predecí
En un mismo manejador de click hacés un setState urgente y otro dentro de startTransition. ¿Cuántas veces se ejecuta la función del componente?
1function alClick() {2 setTexto("nuevo"); // urgente3 startTransition(() => setLista(pesada)); // puede esperar4}Lo que te llevás
Las lanes son un bitmask de prioridades que React marca hacia la raíz y usa para elegir qué renderizar. Se procesa una prioridad por pasada, con su commit y su pintado. Los updates de menor prioridad se saltean y se reaplican después desde baseState, para que el resultado no dependa del reparto. Sobre esto se apoyan useTransition, useDeferredValue y Suspense.
Antes de marcarla, comprobá
- ¿Podés explicar explicar qué es una lane y por qué es un bitmask?
- ¿Podés explicar describir qué pasa con un update de baja prioridad durante una pasada urgente?
- ¿Podés explicar justificar para qué sirve baseState en la cola de updates?