TaskFlow no té molt sentit si només pot mostrar una tasca alhora. Un tauler Kanban real necessita mostrar col·leccions senceres de tasques: totes les pendents, totes les que estan en progrés, totes les d'una persona concreta. Aquesta lliçó explica com recórrer un array de dades dins d'una plantilla Lit utilitzant Array.map, quins problemes d'identitat poden aparèixer en renderitzar llistes que canvien amb el temps, i per què en aquests casos convé una directiva específica que es detallarà més endavant al curs. Amb aquesta tècnica construiràs <task-list>, el primer component de TaskFlow capaç de mostrar diverses tasques a partir d'una col·lecció de dades.

Contingut

  1. Llistes de plantilles: un array és un valor interpolable
  2. Array.map com a tècnica principal
  3. El problema de la identitat en actualitzar llistes
  4. La directiva repeat i el concepte de key: una menció de passada
  5. Quan n'hi ha prou amb map i quan convé repeat
  6. Construint <task-list>

  1. Llistes de plantilles: un array és un valor interpolable

A la lliçó "El Motor de Plantillas de Lit" es va mencionar, de passada, que un array de valors és un contingut vàlid per interpolar dins d'una plantilla html. És moment d'aturar-se en aquesta idea: quan el resultat d'una interpolació és un array, Lit no el converteix a text (no apareixerà una cosa com "tarea1,tarea2,tarea3" a pantalla); en lloc d'això, renderitza cada element de l'array com si fos una interpolació independent, col·locant cadascun a continuació de l'anterior.

render() {
  return html`
    <ul>
      ${['Comprar pan', 'Revisar el PR', 'Llamar al cliente']}
    </ul>
  `;
}

Aquest exemple, encara que poc útil tal com és, ja demostra el comportament: els tres textos de l'array apareixerien un darrere l'altre dins de l'<ul>, sense cap etiqueta <li> que els envolti individualment, perquè l'array conté només text pla. El cas realment útil apareix quan, en lloc d'un array de textos solts, s'interpola un array de plantilles html, una per cada element de les dades originals. Aquí és on entra Array.map.

  1. Array.map com a tècnica principal

El patró estàndard per renderitzar una llista a Lit consisteix a utilitzar el mètode map dels arrays, ja disponible en JavaScript estàndard sense cap dependència de Lit, per transformar un array de dades en un array de plantilles html:

import { LitElement, html } from 'lit';

class ListaDeTareas extends LitElement {
  constructor() {
    super();
    this.tareas = ['Comprar pan', 'Revisar el PR', 'Llamar al cliente'];
  }

  render() {
    return html`
      <ul>
        ${this.tareas.map((tarea) => html`<li>${tarea}</li>`)}
      </ul>
    `;
  }
}

Analitzem l'expressió clau: this.tareas.map((tarea) => html\

  • ${tarea}
  • `). El mètode maprecorre cada element de l'arraythis.tareasi, per cada un, executa la funció indicada, que en aquest cas retorna una plantillahtmlamb un
  • que conté aquell text. El resultat demapno és ja l'array de textos original, sinó un **nou array de plantilleshtml**, una per cada tasca. Aquest nou array és exactament el tipus de valor descrit a l'apartat 1: un array el contingut del qual són plantilles, així que Lit el renderitza col·locant cada
  • un darrere l'altre dins de l'
      `.

      Aquest patró és tan habitual a Lit (i, en general, a qualsevol framework modern d'interfícies basat en JavaScript) que convé memoritzar-lo com una construcció d'una sola línia: array.map((elemento) => html\...`)`. Es pot utilitzar en qualsevol punt d'una plantilla on tingui sentit repetir una estructura per cada element d'una col·lecció: files d'una taula, opcions d'un desplegable, targetes d'un tauler, elements d'un menú.

      Si els objectes de la col·lecció són més complexos que simples cadenes de text, el patró funciona exactament igual, accedint a les propietats de cada objecte dins de la funció:

  • render() {
      return html`
        <ul>
          ${this.tareas.map((tarea) => html`
            <li>${tarea.titulo} — ${tarea.estado}</li>
          `)}
        </ul>
      `;
    }

    1. El problema de la identitat en actualitzar llistes

    Mentre una llista es renderitza una única vegada i mai més torna a canviar, map no presenta cap problema. Els inconvenients apareixen quan la llista canvia amb el temps: s'afegeixen tasques, s'eliminen, o es reordenen (per exemple, en arrossegar una targeta d'una columna a una altra en un tauler Kanban, una cosa que TaskFlow farà en mòduls posteriors).

    Quan Lit torna a executar render() amb un array diferent (encara que només hagi canviat lleugerament respecte a l'anterior), i aquest array es processa amb map com a l'apartat 2, Lit compara, per defecte, la posició de cada plantilla resultant amb la posició que ocupava al renderitzat anterior, no el contingut lògic de cada element. És a dir: "la plantilla que estava a la posició 3 l'última vegada" es compara amb "la plantilla que està a la posició 3 aquesta vegada", sense tenir en compte si es tracta realment de la mateixa tasca original o d'una completament diferent que simplement ha caigut en aquesta posició després de reordenar l'array.

    A la pràctica, això pot produir dos tipus de problemes:

    • Ineficiència: si s'insereix una tasca nova al principi d'una llista de cent elements, totes les posicions es desplacen una cap avall. Comparant per posició, Lit pot acabar actualitzant el contingut de les cent targetes (perquè "l'element a la posició N" ha canviat a les cent posicions), en lloc de reconèixer que noranta-nou targetes són exactament les mateixes d'abans i només cal inserir-ne una nova al principi.
    • Pèrdua d'estat del DOM: si algun node dins d'una d'aquestes plantilles tenia estat propi del navegador (per exemple, el focus d'un camp de text, una animació en marxa, el text seleccionat per l'usuari), reordenar per posició pot fer que aquest estat "salti" a un element lògic diferent del que el tenia originalment, perquè Lit, en no saber quin element és "el mateix d'abans", pot reutilitzar el node físic d'una posició per representar ara dades diferents.

    1. La directiva repeat i el concepte de key: una menció de passada

    Per resoldre aquest problema, Lit ofereix una directiva anomenada repeat, pensada específicament per a llistes que canvien amb el temps (s'insereixen, eliminen o reordenen elements). La seva forma bàsica és la següent:

    import { repeat } from 'lit/directives/repeat.js';
    
    render() {
      return html`
        <ul>
          ${repeat(
            this.tareas,
            (tarea) => tarea.id,
            (tarea) => html`<li>${tarea.titulo}</li>`
          )}
        </ul>
      `;
    }

    repeat rep tres arguments: l'array de dades, una funció que calcula una clau única (key) per cada element —normalment un identificador estable, com tarea.id—, i una funció que genera la plantilla per cada element, igual que es faria amb map. Gràcies a aquesta clau, Lit pot identificar cada element per la seva identitat lògica real, no per la seva posició a l'array: si una tasca amb id: 'abc' es mou de la posició 3 a la posició 0, repeat reconeix que continua sent la mateixa tasca i mou el node DOM existent, en lloc de destruir-lo i recrear-ne un de nou amb contingut diferent.

    Aquesta directiva, juntament amb la resta del catàleg de directives incorporades a Lit, s'estudia en profunditat al mòdul 7, "Directives i Funcionalitats Avançades de Plantilles". Es menciona aquí, de forma anticipada, únicament perquè sàpigues que existeix una solució concreta al problema descrit a l'apartat 3, i perquè reconeguis la sintaxi si la trobes abans d'arribar a aquest mòdul.

    1. Quan n'hi ha prou amb map i quan convé repeat

    No totes les llistes necessiten repeat; utilitzar-lo "per si de cas" en qualsevol llista afegeix una dependència i una capa de complexitat que de vegades no aporta res. Una guia pràctica per decidir:

    Situació Tècnica recomanada
    La llista es renderitza una vegada i no torna a canviar durant la vida del component Array.map és suficient
    La llista canvia, però sempre es reconstrueix sencera des de zero (per exemple, se substitueix completament l'array a cada actualització, sense conservar elements) Array.map continua sent raonable
    La llista pateix insercions, eliminacions o reordenacions freqüents, i conservar l'estat del DOM de cada element importa (focus, animacions, entrades de formulari) repeat, amb una clau estable com id
    Els elements de la llista són complexos o costosos de tornar a renderitzar, i es vol evitar recalcular els que no han canviat de posició lògica repeat

    A TaskFlow, per exemple, quan més endavant al curs s'implementi arrossegar targetes entre columnes del tauler Kanban, aquest serà exactament l'escenari en el qual repeat aportarà una diferència notable: les targetes es mouran de posició sense perdre la seva identitat ni el seu estat intern. De moment, amb dades d'exemple estàtiques que no canvien durant l'execució del component, Array.map és l'eina adequada i la que s'utilitzarà durant la resta d'aquest mòdul.

    1. Construint <task-list>

    Amb la tècnica de Array.map ja explicada, és el moment de construir el segon component real de TaskFlow: <task-list>. La seva responsabilitat és senzilla: rebre una col·lecció de tasques i renderitzar un <task-card> per cadascuna. Igual que a les lliçons anteriors, la col·lecció es declararà com un camp d'instància simple, no com una propietat reactiva (això arriba al mòdul 3).

    Primer, assegura't que task-card.js exporta o almenys registra el seu element com a les lliçons anteriors (s'assumeix aquí la versió de la lliçó "Renderizado Condicional", amb insígnia d'estat). Després, crea src/components/task-list.js:

    import { LitElement, html } from 'lit';
    import './task-card.js';
    
    class TaskList extends LitElement {
      constructor() {
        super();
        // Array d'instància simple, encara sense reactivitat real (mòdul 3).
        this.tareas = [
          { id: 1, titulo: 'Preparar la demo del sprint', estado: 'en-progreso' },
          { id: 2, titulo: 'Revisar el PR de autenticación', estado: 'pendiente' },
          { id: 3, titulo: 'Desplegar a producción', estado: 'hecha' },
        ];
      }
    
      render() {
        return html`
          <section>
            <h2>Mis tareas</h2>
            <div class="lista">
              ${this.tareas.map(
                (tarea) => html`<task-card></task-card>`
              )}
            </div>
          </section>
        `;
      }
    }
    
    customElements.define('task-list', TaskList);

    Hi ha un detall important que convé assenyalar explícitament en aquest punt del curs: a la línia html\`dins delmap, s'està creant una instància de per cada tasca de l'array, però **encara no se li està passant cap dada concreta d'aquesta tasca**. Com quecontinua sense tenir propietats reactives reals, encara no existeix una forma neta de dir-li "mostra el títol d'*aquesta* tasca en concret"; per això, en aquest punt del curs, les tres targetes mostrades peres veuran totes idèntiques, amb els valors fixos que` porta al seu propi constructor.

    Aquesta limitació, precisament, és l'argument més clar per justificar per què el mòdul 3 és imprescindible: en quant <task-card> declari propietats reactives reals, es podrà escriure una cosa com html\<task-card .titulo="${tarea.titulo}" .estado="${tarea.estado}">`(utilitzant la sintaxi de propietats amb punt vista a la lliçó anterior), i cada targeta mostrarà llavors les dades de la seva pròpia tasca. És important que quedi clara aquesta limitació ara, en lloc de deixar-la com una sorpresa confusa:` ja sap recórrer un array i generar una targeta per element, que és la part d'aquest mòdul, però connectar cada targeta amb les seves pròpies dades és la part del mòdul 3.

    Per utilitzar el nou component, actualitza index.html:

    <!DOCTYPE html>
    <html lang="es">
    <head>
      <meta charset="UTF-8">
      <title>TaskFlow</title>
      <script type="module" src="/src/components/task-list.js"></script>
    </head>
    <body>
      <h1>TaskFlow</h1>
      <task-list></task-list>
    </body>
    </html>

    Fixa't que index.html ja no necessita importar task-card.js directament: n'hi ha prou que task-list.js l'importi (import './task-card.js';), perquè aquest import registra igualment l'element <task-card> al navegador, i <task-list> l'utilitza internament a la seva pròpia plantilla. En recarregar la pàgina, hauries de veure el títol "Mis tareas" seguit de tres targetes, una per cada element de l'array this.tareas, encara que les tres mostrin, de moment, el mateix contingut d'exemple.

    Errors Comuns i Consells

    • Oblidar l'import del component fill: si <task-list> no importa task-card.js (directament o de forma indirecta, a través d'un altre fitxer que sí ho faci), el navegador no sabrà què fer amb l'etiqueta <task-card> i la tractarà com un element desconegut, sense cap error visible més enllà que no es vegi res a dins.
    • Esperar que cada <task-card> mostri dades diferents ja en aquest mòdul: com s'ha explicat a l'apartat 6, sense propietats reactives encara no existeix una forma neta de passar dades diferents a cada instància; les targetes es veuran iguals fins al mòdul 3, i això és intencionat.
    • Utilitzar l'índex de map com si fos una clau estable: és temptador escriure this.tareas.map((tarea, indice) => ...) i pensar en indice com un identificador únic de cada tasca. No ho és: l'índex depèn de la posició actual a l'array, que canvia si es reordena o s'insereix un element al principi. Per identificar de forma estable un element (per exemple, en utilitzar repeat més endavant al curs), cal utilitzar un identificador propi de les dades, com tarea.id.
    • Renderitzar llistes enormes sense cap estratègia de paginació o virtualització: Array.map funciona bé per a llistes de mida raonable (desenes o fins i tot uns pocs centenars d'elements), però renderitzar milers d'elements de cop pot tornar-se lent independentment de la biblioteca utilitzada. Aquesta consideració de rendiment a gran escala es reprèn al mòdul 9, "Proves i Bones Pràctiques".

    Exercicis

    1. Afegeix una quarta tasca a l'array this.tareas de <task-list> i comprova que apareix una quarta targeta en recarregar la pàgina.
    2. Modifica render() de <task-list> perquè, abans de la llista de targetes, mostri un paràgraf amb el número total de tasques, utilitzant this.tareas.length interpolat directament (sense necessitat de map, ja que és un únic valor).
    3. Investiga (consultant la documentació oficial de Lit sobre la directiva repeat, o l'apartat 4 d'aquesta lliçó) què passaria de diferent, en termes de quins nodes del DOM es reutilitzen, si s'utilitzés repeat amb tarea.id com a clau en lloc de Array.map, en inserir una nova tasca al principi de l'array. Redacta la resposta amb les teves paraules, sense necessitat de programar-ho encara.

    Solucions

    this.tareas = [
      { id: 1, titulo: 'Preparar la demo del sprint', estado: 'en-progreso' },
      { id: 2, titulo: 'Revisar el PR de autenticación', estado: 'pendiente' },
      { id: 3, titulo: 'Desplegar a producción', estado: 'hecha' },
      { id: 4, titulo: 'Actualizar la documentación', estado: 'pendiente' },
    ];

    En recarregar la pàgina, Array.map genera automàticament una quarta plantilla <task-card>, sense necessitat de tocar la resta de render().

    render() {
      return html`
        <section>
          <h2>Mis tareas</h2>
          <p>Total: ${this.tareas.length} tareas</p>
          <div class="lista">
            ${this.tareas.map((tarea) => html`<task-card></task-card>`)}
          </div>
        </section>
      `;
    }
    1. Amb Array.map, en inserir una tasca nova al principi de l'array, totes les tasques existents passen a ocupar una posició diferent de la que tenien (la que era la posició 0 passa a ser la 1, i així successivament); com que Lit compara per posició en aquest cas, és possible que reconstrueixi o actualitzi el contingut de totes les targetes existents, no només que insereixi una nova al principi. Amb repeat i tarea.id com a clau, Lit identifica cada tasca pel seu identificador, independentment de la posició que ocupi a l'array; en inserir una tasca nova al principi, reconeixeria que les altres tasques són les mateixes d'abans (mateix id) i simplement inseriria un nou node DOM al principi, sense tocar ni reconstruir els nodes ja existents de les altres targetes.

    Conclusió

    En aquesta lliçó has aprés a renderitzar col·leccions de dades dins d'una plantilla Lit combinant Array.map amb interpolació d'arrays de plantilles html, la tècnica estàndard per a llistes que no canvien de forma complexa durant la vida del component. També has entès per què les llistes que es reordenen o modifiquen amb freqüència poden patir problemes d'identitat si es comparen només per posició, i per què existeix la directiva repeat amb clau (key) per a aquests casos, el detall de la qual s'estudiarà al mòdul 7. Amb aquesta base, TaskFlow ja compta amb <task-list>, capaç de recórrer un array de tasques d'exemple i generar un <task-card> per cadascuna, encara que encara sense poder-li passar dades diferents a cada targeta: aquesta peça queda pendent, de forma conscient, per al mòdul 3.

    A l'última lliçó d'aquest mòdul, "El Ciclo de Renderizado", faràs un pas enrere per entendre una cosa que has estat donant per fet fins ara: com i quan decideix Lit exactament quan tornar a executar render(), per què aquest procés és asíncron, i per què mai s'ha de modificar el DOM a mà dins d'aquest mètode, tancant així el mòdul abans de fer el salt a les propietats reactives de veritat al mòdul 3.

    Curs de Lit

    Mòdul 1: Introducció a Lit i Web Components

    Mòdul 2: Plantilles Reactives i Renderitzat

    Mòdul 3: Propietats i Estat Reactiu

    Mòdul 4: Estils en Components Lit

    Mòdul 5: Esdeveniments i Comunicació entre Components

    Mòdul 6: Cicle de Vida i Comportament Avançat

    Mòdul 7: Directives i Funcionalitats Avançades de Plantilles

    Mòdul 8: Integració, Interoperabilitat i Desplegament

    Mòdul 9: Proves i Bones Pràctiques

    Mòdul 10: Projecte: Construint TaskFlow

    © Copyright 2026. Tots els drets reservats