wandres.dev
CONTROL FLOW: FOR E INDEX · listas keyed vs por índice

Index: mapeo por posición

Index mapea por posición, no por referencia: el nodo de cada índice es estable y lo que cambia es su contenido. Por eso el item es un accessor reactivo y el índice un número fijo. Es la elección correcta para primitivos, inputs editables y listas de tamaño estable donde la identidad no vive en el dato sino en el lugar.

⏱ 14 min

Donde For clavea por referencia, Index clavea por posición. El invariante deja de ser el objeto y pasa a ser el sitio: el nodo del índice 0 es siempre el mismo nodo, y lo que varía es su contenido. De esa inversión se sigue todo lo demás, empezando por la firma del callback, que queda del revés respecto a For: aquí el item es un accessor —un signal— y el index un número corriente. Index no sigue objetos; sostiene posiciones y les inyecta valores.

🎯 Al terminar esta lección sabrás
  • Entender que Index fija el nodo por posición y hace reactivo su contenido.
  • Ver por qué el item es un accessor item() y el índice un número estático.
  • Reconocer los tres escenarios donde Index es la elección correcta.
  • Contrastar el comportamiento de Index con el de For ante un cambio de valor.

El nodo es la posición, el valor es la señal

Index también se suscribe a each, pero su reconciliación es de otra naturaleza: compara longitudes y posiciones, no referencias. Para cada índice i que ya existía, no recrea nada; empuja el valor nuevo dentro del signal que representa esa fila, de modo que solo se re-ejecutan los bindings de grano fino que leen item(). Crea nodos únicamente cuando el array crece y los desecha cuando mengua. Reordenar el array bajo Index no mueve ningún nodo: la posición 0 se queda donde está y su signal simplemente recibe lo que ahora ocupe el índice 0.

import { Index, createSignal } from "solid-js";

function Marcadores() {
  const [puntos, setPuntos] = createSignal([10, 20, 30]);

  return (
    <ul>
      <Index each={puntos()}>
        {(punto, i) => <li>fila {i}: {punto()}</li>}
      </Index>
    </ul>
  );
}

Fíjate en los paréntesis: punto() con paréntesis porque es un accessor reactivo; i sin paréntesis porque es un número fijo que nunca cambia para esa fila. Es exactamente el espejo de For. La regla que gobierna ambos es la misma —lo reactivo es lo que puede cambiar mientras la fila permanece— y aquí lo que cambia es el valor, no el sitio.

Por posición, no por referencia

La diferencia se vuelve tangible cuando muta un solo elemento. Supón que el valor de la posición 0 pasa de 10 a 99. Bajo Index, el nodo de esa fila no se toca: su signal recibe 99 y solo el nodo de texto que lo muestra se actualiza. Bajo For, en cambio, 10 y 99 son claves distintas —los primitivos se comparan por valor—, así que la fila entera se desecha y se recrea. Un mismo cambio, dos mundos.

flowchart TD
V[cambia el valor de la posicion 0] --> I[Index]
V --> F[For]
I -->|mismo nodo| S[actualizar el signal item de esa fila]
F -->|valor distinto es clave distinta| C[desechar el nodo viejo y crear uno nuevo]
S --> P[nodo estable el foco y el scroll sobreviven]
C --> Q[nodo recreado se pierde foco y estado del DOM]
style S fill:#a6e3a1,color:#11111b
style P fill:#a6e3a1,color:#11111b
style C fill:#f38ba8,color:#11111b
style Q fill:#f38ba8,color:#11111b

La lección operativa: cuando la identidad vive en la posición y no en el dato, Index conserva justo lo que For destruiría. Un input en la fila 0 mantiene su foco, su cursor y su selección mientras su valor cambia por debajo, porque el elemento del DOM es literalmente el mismo. Ese es el terreno natural de Index.

Cuándo Index es la elección correcta

🔢

Listas de primitivos

Números, strings y booleanos no tienen identidad de referencia estable. Keyear por referencia no aporta nada y sí provoca recreaciones al cambiar un valor. Index mantiene el nodo y solo mueve el dato a través del signal.

⌨️

Inputs y campos editables

Si cada fila es un input cuyo valor edita el usuario, quieres que el nodo se quede quieto —foco, cursor, selección, composición IME— mientras el valor fluye. Index fija el nodo por posición y el contenido reacciona sin remontar.

📏

Tamaño estable, sin reordenar

Cuando la lista rara vez cambia de longitud y nunca se reordena —un tablero de N celdas, una rejilla fija, un formulario de campos conocidos—, la posición ES la identidad natural. Index es más directo y evita el coste del diff por referencia.

El patrón común a los tres casos es la ausencia de una identidad estable en el propio dato. Cuando no hay un objeto que seguir —porque son primitivos, o porque lo que importa es “la casilla número 3”, no “la entidad X”— pedirle a For que rastree referencias es pedirle que persiga fantasmas. Index acepta ese mundo tal cual: aquí las cosas se definen por dónde están.

⚠️
El reverso: no uses Index para objetos reordenables

Index es la herramienta equivocada para una lista de objetos con estado por fila que se reordena. Como el nodo pertenece a la posición y no al objeto, al reordenar el estado del DOM se queda pegado al sitio, no viaja con la entidad: abres la fila del objeto B, mueves B al final y la fila abierta sigue siendo la segunda, ahora con otro objeto. Ese síntoma —el estado que se queda en su posición— es la señal inequívoca de que necesitabas For. Lo verás como error clásico en 16.5.

La reconciliación de Index, paso a paso

Index no difea referencias: compara la longitud nueva con la vieja y actúa por tramos. Para los índices que ya existían, reutiliza el nodo y escribe el valor nuevo en su signal; si el array creció, añade nodos al final; si menguó, retira los sobrantes y limpia sus owners. Nunca reordena ni mueve: la posición k es siempre el mismo elemento del DOM mientras la lista tenga al menos k+1 elementos.

const [filas, setFilas] = createSignal([10, 20, 30]);

// crece: se ANADE un nodo en la posicion 3, los otros no se tocan
setFilas([10, 20, 30, 40]);

// cambia un valor: el nodo 0 se queda, su signal pasa de 10 a 99
setFilas((f) => [99, ...f.slice(1)]);

// mengua: se RETIRA el ultimo nodo y se limpia su owner
setFilas((f) => f.slice(0, 2));

De aquí se deduce el perfil de coste. Index es óptimo cuando los cambios son de valor o de longitud por el extremo final, y caro cuando insertas o borras por el medio, porque desplaza el sentido de todas las posiciones siguientes: cada signal recibe un valor distinto y, en la práctica, toda la cola se actualiza. Insertar al principio bajo Index reescribe todas las filas. Ese caso pide For.

Reordenar es el mismo fenómeno visto de otro ángulo. Como los nodos están anclados, “reordenar” solo significa que cada posición recibe otro valor y todas las filas mutan a la vez. Si esas filas tenían inputs con foco o estado, el resultado desconcierta: el contenido baila entre nodos que no se movieron. Es la señal más nítida de que la colección tenía identidad de objeto y pedía For.

El escenario donde Index no tiene rival es el formulario dinámico: campos cuyos valores cambian pero cuyas posiciones son fijas.

function Formulario() {
  const [campos, setCampos] = createSignal(["", "", ""]);
  const editar = (i: number, v: string) =>
    setCampos((c) => c.map((x, k) => (k === i ? v : x)));

  return (
    <Index each={campos()}>
      {(campo, i) => (
        <input value={campo()} onInput={(e) => editar(i, e.currentTarget.value)} />
      )}
    </Index>
  );
}
// Index brilla cuando la longitud crece o mengua por el final
setCampos((c) => [...c, ""]);        // anade una fila vacia, las demas intactas
setCampos((c) => c.slice(0, -1));    // quita la ultima, limpia su owner

Y el reverso, insertar por el principio, es su punto débil: setCampos((c) => ["", ...c]) empuja el valor de cada posición a la siguiente, y todos los inputs con foco o texto sin guardar reciben de golpe el valor de su vecino. Si tu lista inserta por el medio con datos que el usuario está editando, esa es la señal de que necesitabas identidad de objeto y, por tanto, For.

ℹ️
Index también acepta fallback y each nulo

Igual que For, Index admite una prop fallback para el array vacío y trata null o undefined en each como lista sin elementos. Y cada fila abre su propio owner, con limpieza automática al retirarse. Lo único que cambia entre ambos es la clave: For reconcilia por referencia, Index por posición; el resto de la maquinaria es común.

💡
Index envuelve indexArray

Igual que For se apoya en mapArray, Index es la cara visible de indexArray, el primitivo que mantiene un signal por posición. Si necesitas la misma semántica posicional fuera del JSX —mapear posiciones a valores derivados que reaccionan por celda— indexArray te la da sin el envoltorio del componente.

Sostener el sitio o seguir la cosa

Toda la elección entre Index y For cabe en una pregunta ontológica: ¿la identidad de una fila reside en el dato o en el lugar? For responde “en el dato”: crea un nodo por objeto y lo arrastra a donde el objeto vaya, preservando su estado como equipaje. Index responde “en el lugar”: crea un nodo por posición, lo ancla al suelo y le va cambiando el valor por debajo mediante un signal. Ninguna respuesta es superior; son dos físicas distintas para dos clases de colección. Los primitivos —números, strings— no tienen alma de referencia: un 7 es indistinguible de otro 7, así que su identidad solo puede ser posicional, y Index los trata con naturalidad mientras For tropieza con claves duplicadas. Las entidades con id sí tienen alma: quieres que su fila las siga por la lista, y ahí For brilla mientras Index pegaría el estado al índice equivocado. Interiorizar esta dualidad convierte una decisión que parecía de rendimiento en una de modelado: no eliges el componente más rápido, eliges el que dice la verdad sobre dónde vive la identidad de tus datos. Acertar en esa pregunta hace que el estado, el foco y las transiciones caigan en su sitio sin que tengas que orquestarlos.

⚔️ Ancla el nodo, mueve el valor
  1. Renderiza un array de números con Index y muestra fila {i}: {punto()}. Cambia el valor de la posición 1 y confirma que solo ese texto se actualiza.
  2. Sustituye Index por For sobre los mismos primitivos y observa qué pasa al repetir un valor: aparecen avisos de clave y comportamientos raros. Anota por qué.
  3. Convierte cada fila en un <input> bajo Index, escribe en el de en medio y cambia por código el valor de otra fila; comprueba que no pierdes el foco.
  4. Repite con For y observa cómo, al cambiar el valor, el input se remonta y el foco se pierde.
  5. Enuncia en una frase el criterio para elegir Index sobre For.
  6. Añade y quita filas por el final con Index y confirma que las demás no se tocan; luego inserta por el principio y observa cómo todos los valores se desplazan de nodo.