El coste de reconciliación: mover vs recrear
For y Index evitan repintar la lista entera, pero por mecanismos opuestos: For difea por referencia y mueve nodos existentes; Index difea por longitud y actualiza signals en su sitio. Mover un nodo preserva el estado invisible que cuelga de él; recrearlo lo destruye. Esta lección disecciona ambos perfiles de coste.
Difear el delta, no repintar la lista
Ni For ni Index re-ejecutan el mapeo entero cuando el array cambia. For corre un diff keyed que produce el conjunto mínimo de operaciones —crear, mover, eliminar— usando la referencia como clave; los algoritmos modernos minimizan el número de movimientos buscando la subsecuencia estable más larga. Index compara longitudes y, para cada posición que ya existía, se limita a escribir en su signal. En ambos casos, las filas intactas no re-ejecutan su callback ni su ámbito: su coste de actualización es exactamente cero.
Compáralo con lo que ocurre si escribes el mapeo a mano en el JSX:
// ANTIPATRON: sin keying, reconstruye TODO el subarbol en cada cambio<ul> {items().map((it) => <li>{it.texto}</li>)}</ul>Cada vez que items() cambia de referencia, esta expresión reevalúa el map completo, fabrica un array de li nuevos y sustituye el bloque entero: desmonta todo lo viejo y monta todo lo nuevo. Se pierde el estado de cada fila, se disparan de nuevo todos los efectos y el navegador rehace layout de la lista completa. For existe precisamente para reemplazar ese borrón y cuenta nueva por un diff quirúrgico.
La versión correcta delega ese trabajo en For, que mantiene la correspondencia entre cada objeto y su nodo entre actualizaciones:
// CORRECTO: For difea por referencia y solo toca el delta<ul> <For each={items()}> {(it) => <li>{it.texto}</li>} </For></ul>La diferencia no es de estilo: es la distancia entre reconstruir N nodos y reubicar unos pocos. En una lista que se ordena, se filtra o se pagina, ese contraste decide si la interfaz responde al instante o tartamudea en cada cambio.
Mover preserva, recrear destruye
Un nodo del DOM transporta estado que no está en tus datos: el foco y la selección de texto, la posición de scroll interna, el valor de un input no controlado, una transición CSS en curso, la reproducción de un <video>, la composición del IME. Ese estado vive en el elemento, no en el modelo. Cuando For mueve un nodo con insertBefore, todo eso viaja intacto. Cuando una estrategia equivocada lo recrea —lo elimina y crea otro—, todo eso se reinicia a cero.
Este es el motivo profundo por el que la elección For frente a Index no es cosmética. No estás optimizando milisegundos: estás decidiendo si el foco del usuario sobrevive a un reordenamiento, si un vídeo sigue reproduciéndose cuando su fila sube, si una animación de entrada no se corta a la mitad. La reconciliación correcta es la que mueve el nodo que ya contiene ese estado, en vez de tirarlo y empezar de nuevo.
Un caso concreto lo hace tangible. Imagina filas con un input sin controlar y un vídeo, y un botón que reordena la lista:
<For each={ordenadas()}> {(fila) => ( <li> <input placeholder="nota" /> {/* conserva foco y texto al reordenar */} <video src={fila.clip} /> {/* sigue reproduciendo al moverse */} </li> )}</For>Al reordenar, For mueve estos li con insertBefore: el input mantiene el texto sin guardar y el vídeo no se reinicia. La misma escena con un map en crudo vaciaría el input y rebobinaría el vídeo en cada orden, porque cada fila sería un elemento recién fabricado.
For frente a Index en coste
Los dos evitan el repintado total, pero sus perfiles son distintos y complementarios:
La regla mental que decide entre ambos vuelve a ser la pregunta de identidad: si la fila debe seguir a un objeto por sus saltos de posición, quieres que el nodo se mueva con él, y eso es For. Si la fila debe quedarse en su sitio mientras su contenido cambia, quieres que el nodo se ancle y solo mute su valor, y eso es Index. Elegir mal no solo cuesta rendimiento: cuesta el estado invisible del nodo, que es a menudo lo que el usuario está tocando en ese instante.
En términos de complejidad, Index actualiza una celda en tiempo constante —escribe un signal— mientras For paga un recorrido lineal para diferenciar. Pero el asintótico engaña: el término dominante en una UI no es el diff, es el trabajo del DOM que cada estrategia evita o provoca. For gasta un poco más en decidir para gastar mucho menos en construir; Index no decide nada y por eso es imbatible cuando de verdad solo cambian valores.
Ninguno de los dos, conviene insistir, re-ejecuta el cuerpo de las filas que no cambian. Esa es la propiedad que ambos comparten frente al map ingenuo, y la que hace del control flow de Solid algo cualitativamente distinto de repintar una lista.
El algoritmo: mínimo de movimientos
For no reubica nodos a lo bruto. Su diff conserva el máximo de posiciones ya correctas y solo mueve lo imprescindible, apoyándose en una idea equivalente a la subsecuencia creciente más larga: las filas que ya guardan el orden relativo correcto no se tocan, y solo las que lo rompen se desplazan. Ante un reordenamiento parcial, el número de operaciones del DOM es proporcional a lo que de verdad cambió de sitio, no a la longitud de la lista.
// [A B C D E] -> [A C B D E]// solo una fila se mueve; A D E permanecen intactas, sin re-ejecutar nada<For each={items()}>{(it) => <Fila dato={it} />}</For>Considera una inserción al principio de una lista de mil filas. Con un map en crudo, las mil se desmontan y se vuelven a montar. Con For, la fila nueva se crea y las mil viejas se reconocen por referencia y se reubican un puesto, sin re-ejecutar sus cuerpos. Con Index, en cambio, la posición 0 recibe el valor nuevo y las mil siguientes reciben, cada una, el valor de su vecina anterior: mil signals actualizados. El mismo gesto tiene tres costes radicalmente distintos.
Hay un reverso honesto: For paga un coste fijo por su estructura de reconciliación —memoriza el mapeo de referencia a nodo y recorre el array para diferenciar—. En listas diminutas y de valores —tres etiquetas, dos strings que nunca reordenan— esa maquinaria es sobreingeniería, y Index resulta más directo. La regla de siempre decide: si hay identidad de objeto y reordenamientos, el diff de For se amortiza con lo que ahorra en recreaciones; si no los hay, Index gana en simplicidad.