For: mapeo keyed por referencia
For mapea un array a nodos usando la referencia de cada objeto como clave de identidad. Cada item se renderiza una sola vez y su nodo DOM, junto con su ámbito reactivo y su estado, sigue a ese objeto por la lista aunque cambie de posición. Sin prop key, sin comparar índices: la identidad es la referencia en memoria.
La referencia es la clave
Cuando el compilador ve <For each={tareas()}>, no genera un bucle que recorra el array en cada cambio. Genera una suscripción: For observa la expresión each y, cuando esta devuelve un array distinto, dispara su algoritmo de reconciliación. Ese algoritmo no compara texto ni posiciones; compara referencias de objeto con igualdad estricta. Para cada elemento del array nuevo formula una única pregunta —¿esta referencia ya existía en el array anterior?— y de la respuesta se derivan tres caminos: si existía, reutiliza el nodo y el ámbito que ya tenía; si es nueva, invoca el callback para fabricarlos; si una referencia del array viejo ya no aparece, desecha su nodo y ejecuta su limpieza.
import { For, createSignal } from "solid-js";type Tarea = { id: number; texto: string };function Lista() { const [tareas, setTareas] = createSignal<Tarea[]>([ { id: 1, texto: "leer" }, { id: 2, texto: "escribir" }, ]); return ( <ul> <For each={tareas()}> {(tarea) => <li>{tarea.texto}</li>} </For> </ul> );}De ahí el nombre keyed: la clave de cada fila no la pones tú con una prop, la aporta la propia identidad del objeto en memoria. Dos objetos con los mismos campos pero distinta referencia son, para For, dos filas distintas. El mismo objeto movido de sitio es, para For, la misma fila. Esta es la diferencia cruz con React, donde debes suministrar key a mano y donde olvidarla degrada el diff a una comparación por posición, frágil y propensa a que el estado salte de fila.
Cada item se renderiza una vez
La firma del callback es (item, index) => JSX, y cada parte encierra una decisión de diseño. El item es un valor estático, no un accessor: lo lees como tarea.texto, sin paréntesis. Puede permitírselo porque, por construcción, la referencia de una fila nunca cambia durante su vida; si el objeto fuese reemplazado por otro, sería una fila distinta con su propio nodo. El index, en cambio, es un accessor —lo llamas index()— porque la posición sí puede variar cuando la lista se reordena, y debe seguir siendo reactiva sin recrear la fila. Esa asimetría la disecciona 16.4.
<For each={tareas()}> {(tarea, indice) => { // este cuerpo corre UNA vez por cada referencia nueva const [abierta, setAbierta] = createSignal(false); return ( <li onClick={() => setAbierta(!abierta())}> {indice()}. {tarea.texto} {abierta() ? "▼" : "▶"} </li> ); }}</For>La consecuencia más valiosa de “una vez por referencia” es que cada fila puede alojar estado local que sobrevive a los reordenamientos. El signal abierta pertenece al ámbito de esa fila; cuando For mueve el nodo, mueve con él su ámbito entero —sus signals, sus memos, sus efectos, la posición de scroll, el foco de un input—. Nada de eso se reinicia, porque nada se recrea. En React ese estado vive fuera de la lista o depende de que la key sea perfecta; aquí cuelga físicamente del nodo que viaja.
Seguir al objeto aunque se mueva
Imagina la lista [A, B, C] y que el usuario la reordena a [C, A, B]. For no vuelve a ejecutar tres callbacks ni reconstruye tres li. Recorre el array nuevo, reconoce las tres referencias del anterior y emite un plan mínimo de operaciones del DOM: mover, no recrear. Los tres nodos siguen siendo los mismos objetos del DOM, con su estado intacto, reubicados con insertBefore.
Solo tres eventos tocan el DOM de verdad: una referencia nueva crea, una referencia ausente desecha, y todo lo demás se mueve. Las filas cuya referencia persiste jamás re-ejecutan su callback: su coste de actualización es cero. Por eso For escala con el delta de la lista, no con su tamaño.
La firma completa: fallback y ámbito de fila
For es más que un mapeo. Su prop fallback se renderiza cuando el array está vacío, y For trata null o undefined en each como lista vacía sin lanzar errores. Eso jubila el ternario lista.length ? ... : ... que tanto ensucia el JSX heredado de React:
<For each={tareas()} fallback={<p>No hay tareas todavía.</p>}> {(tarea) => <li>{tarea.texto}</li>}</For>Bajo el callback late una segunda capa. Su cuerpo se ejecuta dentro de un owner: el ámbito reactivo que Solid usa para rastrear pertenencia y limpieza. Todo lo que crees ahí —un createEffect, un createMemo, un onCleanup, una suscripción— pertenece a esa fila. Mientras la referencia del objeto viva en el array, su owner vive; cuando desaparece, For lo destruye y ejecuta sus limpiezas de abajo arriba. Una fila no es un nodo suelto: es un subárbol reactivo con ciclo de vida propio, atado a la identidad de su objeto.
<For each={conexiones()}> {(con) => { const socket = abrir(con.url); // se abre UNA vez, al crear la fila onCleanup(() => socket.cerrar()); // se cierra al salir del array return <li>{con.nombre}</li>; }}</For>Cuando con deja el array, For no solo retira el li: ejecuta su onCleanup y cierra el socket. Esa correspondencia entre la identidad del dato y el ciclo de vida del recurso convierte a For en una herramienta de gestión de recursos, no solo de presentación.