useState por dentro: la cadena de hooks
¿Cómo sabe React cuál de mis useState es cuál?
Al terminar vas a poder explicar
- Dibujar de memoria la estructura que guarda los hooks de un componente
- Explicar qué pasa físicamente al llamar un hook dentro de un if
- Distinguir el argumento inicial de useState del estado real
Mirá este componente y hacete la pregunta en serio:
1function Perfil() {2 const [nombre, setNombre] = useState("Ana");3 const [edad, setEdad] = useState(30);4 const [activo, setActivo] = useState(true);5 // ...6}En el segundo render, React vuelve a ejecutar esta función desde cero. Las tres llamadas a useState son idénticas desde adentro: misma función, sin identificador, sin nombre. ¿Cómo sabe React que la primera tiene que devolver "Ana" y la segunda 30?
No lo sabe por el nombre de la variable — React no ve tus nombres de variables. No lo sabe por el valor inicial — ese argumento se ignora después del montaje.
Lo sabe por el orden en que las llamaste. Y eso no es una convención: es una estructura de datos.
Una lista enlazada, no un array
Cada fiber de un componente de función tiene un campo memoizedState. En un componente con hooks, ese campo apunta al primer hook. Y cada hook apunta al siguiente:
1fiber.memoizedState2 → { kind: "useState", memoizedState: "Ana", queue: {…}, next: ↓ }3 → { kind: "useState", memoizedState: 30, queue: {…}, next: ↓ }4 → { kind: "useState", memoizedState: true, queue: {…}, next: null }No hay índices. No hay nombres. Hay una cadena, y la posición en esa cadena es toda la identidad que tiene un hook.
Montaje y actualización son dos caminos distintos
Cuando el componente se monta, la cadena no existe y hay que construirla. Cuando se actualiza, la cadena ya existe y hay que recorrerla mientras se recorre el código de tu función.
React lleva dos cursores en paralelo: uno sobre la cadena vieja y otro sobre la que va armando. Cada llamada a un hook mueve los dos un lugar. Por eso la cantidad y el orden de llamadas tienen que ser idénticos entre renders: si no, los cursores se desalinean.
Cómo se arma la cadena en el montaje y cómo se clona en la actualización
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.
Buscá mountWorkInProgressHook en la traza del primer render, y updateWorkInProgressHook en el segundo. El texto del panel de abajo te dice, en cada paso, exactamente qué está pasando con los punteros.
El argumento inicial se ignora — de verdad
Esto confunde a mucha gente. En useState(valorInicial), ese argumento sólo se usa una vez, en el montaje. En todos los renders siguientes React ni lo mira: devuelve lo que hay en hook.memoizedState.
Y si le pasás una expresión, esa expresión se evalúa igual en cada render aunque el resultado se tire a la basura:
1// calcularAlgoCaro() corre en CADA render, y su resultado se descarta2const [dato, setDato] = useState(calcularAlgoCaro());3 4// la función se pasa sin ejecutar; React la llama sólo en el montaje5const [dato, setDato] = useState(() => calcularAlgoCaro());A eso se le llama inicializador perezoso. No es una micro-optimización de manual: si la expresión lee de localStorage o parsea un JSON grande, la diferencia se nota.
Antes de seguir · predecí
Este componente monta bien y en el segundo render explota. ¿Qué se rompió, exactamente?
1function Lista({ items }) {2 const [abierto, setAbierto] = useState(false);3 4 if (items.length === 0) {5 return <Vacio />; // ← salida temprana6 }7 8 const [filtro, setFiltro] = useState("");9 return <div>…</div>;10}Lo que te llevás
Los hooks son una lista enlazada colgada de fiber.memoizedState. Su identidad es la posición. El argumento inicial se usa una sola vez. Y las reglas de los hooks — que vamos a ver en la próxima lección — se caen solas de esta estructura.
Antes de marcarla, comprobá
- ¿Podés explicar dibujar de memoria la estructura que guarda los hooks de un componente?
- ¿Podés explicar explicar qué pasa físicamente al llamar un hook dentro de un if?
- ¿Podés explicar distinguir el argumento inicial de useState del estado real?