Saltar al contenido
Framework Ecosystems LabsUn contrato, muchos ecosistemas, la misma prueba.

Clase 085 — Listas y claves#

⬅️ 084 · 📚 Parte 6 · 🎓 Clases · 086 ➡️ Parte 6 — La interfaz: del HTML del servidor al componente · Nivel 🟡 intermedio · Pista frontend (Interfaz y componentes) ✅ Clase construida — 4 implementaciones verificadas contra contrato.json.

🎯 Objetivo#

Pintar una colección, y entender qué pasa cuando el framework no sabe cuál es cuál.

La clave de una lista es la respuesta a una pregunta que el framework se hace en cada actualización: este elemento de ahora, ¿es el mismo que aquel de antes? Contestarla mal no rompe el HTML — mueve el estado de sitio.

📚 Resultados de aprendizaje#

Al terminar podrás:

🧩 La situación#

Tres frutas en una lista. Se invierte el orden.

El HTML resultante es correcto en los dos casos —el contrato lo comprueba— y aun así puede haber un fallo grave: si cada <li> tuviera una casilla marcada o un campo a medio escribir, el contenido se movería y el estado se quedaría.

Ese fallo no está en el HTML, está en cómo el framework decide qué elemento es cuál. Y esa decisión la tomas tú al escribir —o no escribir— la clave.

🧮 El contrato#

# Petición Qué comprueba
1 GET / la lista en orden, con su clave en el marcado
2 GET /?orden=invertido la clave viaja con su elemento
3 GET /?vacia=si una lista vacía no es un error
4 GET /claves-repetidas ¿avisa el framework si dos comparten clave?
5 GET /sin-clave ¿se puede omitir, y avisa alguien?
6 GET /lista.json cómo se escribe aquí, leído del archivo

Los casos 4 y 5 no comprueban un resultado: comprueban si el framework habla. Los avisos de clave no son excepciones — se escriben en la consola y se pierden. Interceptar la consola durante el render es la única forma de meterlos en un contrato, y ese aviso es lo que separa un fallo que se caza en desarrollo de uno que llega a producción.

Y lo que no se comprueba: el fallo en sí. Reordenar y ver el estado quedarse atrás necesita un navegador, y eso es la clase 128. Lo que se verifica aquí es todo lo que decide si ese fallo llega a existir.

<!-- generado: fichas -->

📖 Las palabras que esta clase define#

Si alguna de estas no te dice nada todavía, esta es la clase donde se aprende. Las definiciones viven en el glosario, que reúne las del programa entero.

Palabra Qué significa
Clave de lista (Key) El identificador estable que se le da a cada elemento de una lista para que el framework sepa cuál es cuál al actualizar. Usar el índice como clave produce errores visibles al reordenar o insertar.

🧰 Las piezas de esta clase, una por una#

Antes del código: qué es cada framework, qué versión se está usando y qué hace falta para ejecutarlo. Todo lo de esta sección sale de los archivos reales del repositorio —el catálogo, la receta de arranque y el manifiesto de dependencias de cada ecosistema—, así que no puede quedarse desactualizado sin que la validación lo detecte.

Framework Qué es Desde Licencia Quién lo mantiene
React biblioteca de interfaz de JavaScript/TypeScript (JavaScript) 2013 MIT Meta y colaboradores
Vue framework de interfaz de JavaScript/TypeScript (JavaScript) 2014 MIT proyecto independiente
Svelte framework de interfaz de JavaScript/TypeScript (JavaScript) 2016 MIT proyecto independiente
SolidJS biblioteca de interfaz de JavaScript/TypeScript (JavaScript) 2018 MIT proyecto independiente

🔧 React#

Impuso la idea de la interfaz como función del estado y el árbol virtual. Es una biblioteca, no un framework: no arranca tu aplicación ni define su ciclo de vida.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts

Arrancarla suelta, sin el verificador:

PORT=3000 node server.mjs

Qué hay dentro de su directorio:

Archivo Qué es
Lista.mjs código JavaScript (módulo ES)
datos.mjs código JavaScript (módulo ES)
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
server.mjs código JavaScript (módulo ES)

🔧 Vue#

Adopción progresiva: sirve como etiqueta en una página existente o como framework completo. Su reactividad fina influyó en toda la generación siguiente.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts

Arrancarla suelta, sin el verificador:

PORT=3000 node server.mjs

Qué hay dentro de su directorio:

Archivo Qué es
Lista.mjs código JavaScript (módulo ES)
datos.mjs código JavaScript (módulo ES)
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
server.mjs código JavaScript (módulo ES)

🔧 Svelte#

Mueve el trabajo del navegador al compilador. Cambia el coste de ejecución por dependencia de la fase de construcción: un compromiso, no una mejora gratuita.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts

Arrancarla suelta, sin el verificador:

PORT=3000 node server.mjs

Qué hay dentro de su directorio:

Archivo Qué es
Lista.svelte componente de Svelte
ListaSinClave.svelte componente de Svelte
datos.mjs código JavaScript (módulo ES)
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
server.mjs código JavaScript (módulo ES)

🔧 SolidJS#

Reactividad de grano fino sin árbol virtual: el componente se ejecuta una vez y solo se actualiza lo que leyó el valor cambiado.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts

Arrancarla suelta, sin el verificador:

PORT=3000 node server.mjs

Qué hay dentro de su directorio:

Archivo Qué es
Lista.mjs código JavaScript (módulo ES)
datos.mjs código JavaScript (módulo ES)
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
server.mjs código JavaScript (módulo ES)

Si alguna cadena de herramientas no está en tu máquina, node scripts/doctor.mjs dice cuál falta y con qué comando se instala. No hace falta tenerlas todas: el verificador ejecuta lo que encuentra y declara lo que omitió.

<!-- fin generado: fichas -->

🌐 Las implementaciones — el código a la vista#

Los cuatro comparten los mismos datos — datos.mjs, idéntico en las cuatro:

export const FRUTAS = [
  { id: "a1", nombre: "Aguacate" },
  { id: "b2", nombre: "Berenjena" },
  { id: "c3", nombre: "Calabaza" },
];
 * Cada fruta tiene un `id` que **no depende de su posición**. Esa es la única
 * condición que una clave tiene que cumplir: identificar al elemento, no al
 * hueco donde está hoy.

React · react/Lista.mjs#

    elementos.map((fruta) =>
      h("li", { key: fruta.id, "data-clave": fruta.id }, fruta.nombre),
    ),

Y la explicación de por qué hay dos atributos donde parece que sobra uno:

 * `key` no aparece en el HTML resultante. No es para el navegador — es para
 * React, que la usa al comparar el árbol anterior con el nuevo y decidir qué
 * elementos son «el mismo de antes» y cuáles son nuevos.
 *
 * Por eso `data-clave` está aparte: el contrato necesita VER la identidad en el
 * marcado, y `key` no se ve. Es una diferencia que confunde a mucha gente al
 * inspeccionar el DOM y no encontrarla.

Y el error más común, explicado por su causa:

 * Y por eso usar el índice como clave es un error tan común: el índice
 * identifica el HUECO, no el elemento. Al reordenar, el hueco 0 sigue siendo el
 * hueco 0 y React cree que nada cambió de sitio — así que mueve el contenido y
 * deja el estado local donde estaba.

El hallazgo de esta clase está en lo que React captura y lo que noreact/server.mjs:

 * Los avisos de clave no son excepciones: se escriben en la consola y se
 * pierden. Interceptarla durante el render es la única forma de comprobar por
 * contrato si el framework protesta — y esa protesta es lo que separa un fallo
 * que se detecta en desarrollo de uno que llega a producción.

Ejecutando la clase, la respuesta real:

React caza la clave que falta y no caza la clave repetida. Y de las dos, la repetida es la peor: la que falta produce un aviso ruidoso que alguien acaba leyendo; la repetida no dice nada y empareja mal en silencio.

Vue · vue/Lista.mjs#

      this.elementos.map((fruta) =>
        h("li", { key: fruta.id, "data-clave": fruta.id }, fruta.nombre),
      ),
 * Vue tiene aquí una regla de estilo que merece conocerse porque su verificador
 * la impone: `v-for` sin `key` es un ERROR de linter en la configuración
 * recomendada. No es el framework quien obliga, es la herramienta — y en la
 * práctica funciona igual de bien.

Y eso se ve al ejecutar: el_framework_avisa: false en los dos casos. Vue no dice nada en tiempo de ejecución; quien protesta es el verificador de estilo, antes de que el código llegue a ejecutarse. Es una defensa distinta —más temprana y más fácil de desactivar.

Svelte · svelte/Lista.svelte#

La clave es parte de la sintaxis del bucle:

  {#each elementos as fruta (fruta.id)}
  // Es la forma más visible de las ocho: no es un atributo perdido entre otros,
  // es parte de la estructura del bucle, y quien lee el archivo la ve sí o sí.

Y como Svelte compila, la diferencia entre tener clave y no tenerla se puede medirsvelte/server.mjs:

      // La diferencia no está en el HTML: está en el código generado. Contarlo
      // es la única forma de enseñar que el compilador produce DOS bucles
      // distintos según haya clave o no.

Ejecutando: 21 líneas generadas con clave frente a 18 sin ella. El HTML es idéntico; lo que cambia es el código de actualización — uno que sobrescribe por posición y otro que mueve nodos.

Y en el navegador Svelte es el más duro de los cuatro: dos claves iguales no son un aviso, son una excepción en tiempo de ejecución.

SolidJS · solid/Lista.mjs#

Aquí no hay claves, y no es un olvido:

 * `<For>` —el componente que se usa para listas— identifica cada elemento **por
 * su referencia**, no por una clave que tú escribas. Si el objeto es el mismo
 * objeto, es el mismo elemento; si es otro, es otro.
 *   - no se puede equivocar uno escribiendo la clave, porque no hay clave;
 *   - pero sí se puede equivocar creando objetos nuevos en cada render — y ahí
 *     Solid piensa que la lista entera cambió, aunque los datos sean iguales.
 *
 * El error cambia de sitio: de «puse mal la clave» a «recreé los objetos».

Y la decisión que sustituye a escribir o no la clave:

 * (Existe `<Index>` para el caso contrario: cuando lo que importa es la
 * posición y no el elemento. Elegir entre los dos es la decisión que en las
 * otras siete se toma escribiendo o no una clave.)

Por eso su respuesta al caso de claves repetidas es distinta a las tres anteriores — solid/server.mjs:

      // No hay nada que avisar: la identidad es la referencia del objeto, y dos
      // objetos distintos son distintos aunque su `id` coincida.

🔬 Comparación#

Cómo se escribe ¿Avisa si falta? ¿Avisa si se repite? Quién defiende
React key en el elemento ✅ sí ❌ no el framework, en la consola
Vue :key junto a v-for ❌ no ❌ no el verificador de estilo, antes de ejecutar
Svelte (fruta.id) en el #each ❌ no ✅ excepción el framework, en el navegador
SolidJS no hay clave el modelo: identidad por referencia

Cuatro lecturas:

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 085

Y para ver quién avisa y quién calla:

curl -s http://127.0.0.1:4100/sin-clave

🧪 Reto de transferencia#

  1. Cambia la clave por el índice en una implementación. Los seis casos siguen en verde — porque el HTML no cambia. Esa es exactamente la razón de que el error sobreviva a las revisiones.
  2. Busca en tu proyecto un key={index}. Si la lista se ordena, se filtra o se puede borrar un elemento del medio, tienes un fallo esperando.
  3. Ejecuta /sin-clave en las cuatro y compara qué dice cada una. La tabla de arriba sale de ahí, y comprobarla tú es medio ejercicio.

🔗 Enlaces#

Fuentes#