Al llarg d'aquest mòdul s'ha utilitzat, sense aturar-s'hi massa, una distinció que convé fixar amb precisió abans de tancar el tema: la diferència entre un atribut HTML i una propietat JavaScript. Aquesta lliçó explica aquesta diferència amb detall, aclareix en quina direcció sincronitza Lit per defecte i quan interessa activar la sincronització en el sentit contrari amb reflect: true, i tanca el mòdul aplicant tot el que s'ha après per convertir tareas, a <task-list>, en una propietat reactiva de tipus Array. Amb aquest darrer pas, cada <task-card> del tauler mostrarà per fi les dades de la seva pròpia tasca, resolent la limitació que ha acompanyat TaskFlow des del mòdul 2.

Contingut

  1. Atribut i propietat: dues coses diferents que sovint es confonen
  2. La sincronització per defecte: d'atribut a propietat
  3. reflect: true: sincronitzar també en sentit contrari
  4. Quan interessa reflect: true (i la seva connexió amb el mòdul 4)
  5. Convertint tareas en una propietat reactiva de <task-list>
  6. Passant dades pròpies a cada <task-card>: TaskFlow, per fi, dinàmic
  7. Tancament del mòdul: cap als estils

  1. Atribut i propietat: dues coses diferents que sovint es confonen

És habitual, sobretot venint d'HTML estàtic, donar per fet que "atribut" i "propietat" són la mateixa cosa. A la plataforma web, però, són dos conceptes relacionats però diferents, i la distinció és clau per entendre amb precisió el sistema de reactivitat de Lit.

  • Un atribut és una peça de text que apareix al marcatge HTML, entre les etiquetes d'obertura d'un element: <task-card titulo="Revisar el PR">. Com es va explicar a la lliçó anterior, un atribut sempre és una cadena de text, sense excepció, perquè així ho defineix l'estàndard HTML.
  • Una propietat és un camp de l'objecte JavaScript que representa aquell element al DOM: elemento.titulo. Una propietat pot tenir qualsevol tipus de dada de JavaScript: text, número, booleà, array, objecte, fins i tot una funció.

Aquesta distinció no és exclusiva de Lit ni dels Custom Elements: existeix en qualsevol element natiu del navegador. Un <input> té un atribut value (text) i una propietat value (també text, en aquest cas concret, però per decisió del propi element); un <input type="checkbox"> té un atribut checked (la sola presència del qual indica "marcat", com es va veure amb Boolean a la lliçó anterior) i una propietat checked que és un booleà real de JavaScript. Lit no inventa aquest mecanisme: l'adopta i el generalitza per a les propietats reactives que es declaren a static properties.

Allò que fa static properties és, precisament, dir a Lit: "vull que aquestes dues coses —l'atribut titulo i la propietat titulo— estiguin connectades automàticament, i així és com has de convertir entre una i l'altra".

  1. La sincronització per defecte: d'atribut a propietat

Per defecte, quan es declara una propietat reactiva amb type (o amb state: true, encara que en aquest cas, com es va veure a la lliçó anterior, no hi ha cap atribut amb el qual sincronitzar), la sincronització passa en un únic sentit: d'atribut cap a propietat.

static properties = {
  titulo: { type: String },
};

Amb aquesta declaració, si l'atribut titulo canvia a l'HTML —ja sigui perquè l'element es crea amb aquest atribut, o perquè després es crida elemento.setAttribute('titulo', 'Nuevo valor')—, Lit detecta aquest canvi i actualitza automàticament this.titulo amb el valor convertit. Aquesta direcció és la que s'ha utilitzat, de fet, en tots els exemples d'HTML de les lliçons anteriors d'aquest mòdul (<task-card titulo="...">).

Però el camí invers no passa per defecte: si des de JavaScript s'assigna elemento.titulo = 'Otro valor', la propietat canvia (i dispara l'actualització de render(), com s'ha vist en tot el mòdul), però l'atribut titulo a l'HTML no s'actualitza per reflectir aquest nou valor. Si en aquell moment s'inspeccionés l'element amb les eines de desenvolupament del navegador, l'atribut continuaria mostrant el valor original, encara que la propietat interna i el que es veu en pantalla ja haguessin canviat.

const tarjeta = document.querySelector('task-card');
// L'HTML original era: <task-card titulo="Preparar la demo"></task-card>

tarjeta.titulo = 'Revisar el PR';
// this.titulo ara val 'Revisar el PR', i render() s'actualitza.
// Però l'atribut HTML continua sent titulo="Preparar la demo".

  1. reflect: true: sincronitzar també en sentit contrari

Quan interessa que aquell camí invers també funcioni —que un canvi a la propietat, fet des de JavaScript, es reflecteixi de nou a l'atribut HTML—, s'activa amb l'opció reflect: true, ja esmentada de passada a la primera lliçó del mòdul:

static properties = {
  estado: { type: String, reflect: true },
};

Amb reflect: true, cada vegada que this.estado canvia (per qualsevol via: assignació directa, interpolació amb punt en una plantilla pare, etc.), Lit actualitza automàticament l'atribut estado de l'element al DOM real perquè coincideixi amb el nou valor de la propietat, utilitzant internament una conversió equivalent a toAttribute (la mateixa funció que es va veure al conversor personalitzat de la lliçó anterior, encara que per als tipus suportats de sèrie Lit ja porta la seva pròpia lògica de conversió sense necessitat d'escriure-la a mà).

const tarjeta = document.querySelector('task-card');
tarjeta.estado = 'hecha';
// Amb reflect: true, l'atribut del DOM passa a ser:
// <task-card estado="hecha">

És important remarcar que reflect: true afegeix feina addicional a cada actualització (Lit ha de cridar setAttribute sobre l'element real del DOM, a més d'actualitzar el camp intern de la propietat), així que no convé activar-lo "per si de cas" a totes les propietats; es reserva per als casos concrets on de veritat aporta valor, que es veuen a l'apartat següent.

  1. Quan interessa reflect: true (i la seva connexió amb el mòdul 4)

Hi ha dos motius principals, a la pràctica, pels quals interessa activar reflect: true en una propietat:

  • Poder seleccionar l'element pel seu estat des de fora, per exemple amb document.querySelector('task-card[estado="hecha"]'), una cosa ocasionalment útil per a proves automatitzades o per a eines externes que inspeccionen el DOM sense tenir accés directe a les propietats JavaScript.
  • Poder aplicar estils CSS diferents segons el valor de la propietat, utilitzant un selector d'atribut, una cosa molt més habitual a la pràctica: si l'atribut estado es reflecteix sempre al DOM, es pot escriure a la fulla d'estils del propi component una regla com :host([estado="hecha"]) { opacity: 0.6; }, que aplica un estil diferent segons l'estat actual de la targeta, sense necessitat de calcular classes CSS dinàmicament a la plantilla.

Aquest segon motiu és, de llarg, el més rellevant per a TaskFlow, i és exactament el pont cap al següent mòdul del curs: al mòdul 4, "Estils en Components Lit", s'explicarà amb detall com escriure selectors com :host([atributo]) i per què reflectir una propietat com estado (o urgente) resulta molt útil per poder donar un estil visual diferent a una targeta urgent o a una targeta ja completada, sense necessitat de gestionar classes CSS a mà des de render(). Per ara, en aquest mòdul, n'hi ha prou de saber que reflect: true existeix, què fa exactament, i que la seva utilitat principal apareixerà al pròxim mòdul.

  1. Convertint tareas en una propietat reactiva de <task-list>

Amb tota la teoria de propietats reactives ja coberta en aquest mòdul, és moment de tancar la limitació que ha acompanyat TaskFlow des de la lliçó "Renderitzat de Llistes" del mòdul 2: <task-list> recorre un array this.tareas, però aquest array és un camp d'instància simple, no reactiu, i cada <task-card> que genera es veu idèntica perquè no rep cap dada pròpia.

Recupera src/components/task-list.js i declara tareas com a propietat reactiva de tipus Array, seguint exactament el mateix patró que s'ha utilitzat amb <task-card> al llarg de tot el mòdul:

import { LitElement, html } from 'lit';
import './task-card.js';

class TaskList extends LitElement {
  static properties = {
    tareas: { type: Array },
  };

  constructor() {
    super();
    this.tareas = [
      { id: 1, titulo: 'Preparar la demo del sprint', estado: 'en-progreso', prioridad: 4, urgente: true },
      { id: 2, titulo: 'Revisar el PR de autenticación', estado: 'pendiente', prioridad: 2, urgente: false },
      { id: 3, titulo: 'Desplegar a producción', estado: 'hecha', prioridad: 5, urgente: false },
    ];
  }

  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);

De moment, aquest canvi per si sol ja aporta alguna cosa: com que tareas és ara una propietat reactiva de veritat, se li podria assignar un array completament nou des de fora (listaElemento.tareas = [...]) i <task-list> s'actualitzaria sola, regenerant tota la llista de targetes. Però, com es va assenyalar explícitament en construir <task-list> al mòdul 2, encara falta la peça final: passar les dades de cada tasca a la seva <task-card> corresponent.

  1. Passant dades pròpies a cada <task-card>: TaskFlow, per fi, dinàmic

Amb <task-card> ja dotada de propietats reactives reals (titulo, estado, prioridad, urgente) des de la primera lliçó d'aquest mòdul, ja és possible completar render() a <task-list> utilitzant la sintaxi de propietats amb punt que es va presentar a la lliçó d'expressions i interpolació del mòdul 2:

render() {
  return html`
    <section>
      <h2>Mis tareas</h2>
      <div class="lista">
        ${this.tareas.map(
          (tarea) => html`
            <task-card
              .titulo="${tarea.titulo}"
              .estado="${tarea.estado}"
              .prioridad="${tarea.prioridad}"
              .urgente="${tarea.urgente}"
            ></task-card>
          `
        )}
      </div>
    </section>
  `;
}

Analitzem què passa ara, pas a pas, cada vegada que <task-list> renderitza. this.tareas.map(...) recorre l'array de tasques, tal com es va explicar a la lliçó "Renderitzat de Llistes"; per cada tasca, genera una plantilla <task-card> en la qual quatre propietats s'estableixen mitjançant la sintaxi de punt (.titulo, .estado, .prioridad, .urgente), cadascuna interpolant el camp corresponent de l'objecte tarea actual del recorregut. Com que aquestes quatre propietats estan declarades com a reactives a <task-card> (des de la primera lliçó d'aquest mòdul), assignar-les mitjançant la sintaxi de punt les estableix com a propietats JavaScript reals de cada instància, no com a atributs de text, permetent passar directament el número (prioridad) i el booleà (urgente) sense cap conversió manual.

El resultat, en tornar a carregar la pàgina, és el que s'ha estat esperant des del mòdul 2: <task-list> mostra tres targetes, cadascuna amb el seu propi títol, la seva pròpia insígnia d'estat (calculada per renderInsigniaEstado() a partir de la propietat estado rebuda), la seva pròpia prioritat i, si correspon, el seu propi avís d'urgència. Cada <task-card> és ara un component genuïnament reutilitzable: la mateixa classe TaskCard, parametritzada de manera diferent per cada instància segons les dades que rep del seu pare.

Aquest patró —un component pare (<task-list>) que recorre una col·lecció i passa un fragment de dades a cada component fill (<task-card>) mitjançant propietats— és la manera estàndard en què els components es comuniquen de pare a fill a Lit, i en la pràctica totalitat de les biblioteques modernes d'interfícies. La comunicació en el sentit contrari —que <task-card> avisi <task-list> que l'usuari ha marcat una tasca com a completada, per exemple— requereix un mecanisme diferent, basat en esdeveniments personalitzats, que és exactament el contingut del mòdul 5, "Esdeveniments i Comunicació entre Components". Per ara, amb la comunicació pare-a-fill ja resolta, TaskFlow ja té un tauler de tasques plenament dinàmic pel que fa a mostrar dades, encara que sense cap manera que l'usuari hi interactuï més enllà del clic d'expandir vist a la lliçó anterior.

  1. Tancament del mòdul: cap als estils

Amb aquesta lliçó es completa el mòdul 3, "Propietats i Estat Reactiu". El recorregut ha anat de menys a més: primer, declarar propietats reactives reals amb static properties i entendre què les fa disparar actualitzacions automàtiques; després, distingir entre propietats públiques i estat intern privat amb state: true; a continuació, el catàleg complet de tipus suportats i com escriure un conversor personalitzat per a tipus que no encaixen en aquest catàleg; i, en aquesta última lliçó, la relació exacta entre atributs i propietats, juntament amb l'opció reflect per sincronitzar en ambdós sentits.

TaskFlow, com a resultat de tot el mòdul, ha fet un salt qualitatiu: <task-card> té ara propietats reactives completes (titulo, estado, prioridad, urgente, fechaLimite), un estat intern propi (expandida), i <task-list> rep la seva col·lecció de tasques com una propietat reactiva de tipus Array i passa a cada targeta filla les dades que li corresponen. El tauler, per primera vegada al curs, mostra dades realment diferents a cada targeta i s'actualitza sol quan aquestes dades canvien.

Ara bé, si has estat seguint el curs executant els exemples al navegador, hauràs notat que, malgrat tota aquesta reactivitat de dades, l'aspecte visual de <task-card> i <task-list> continua sent el de l'HTML sense cap estil: sense colors, sense espaiats acurats, sense cap diferenciació visual clara entre una targeta urgent i una que no ho és. Les dades ja són dinàmiques; falta que es vegin bé. Aquesta és, precisament, la tasca del mòdul 4, "Estils en Components Lit", on aprendràs a utilitzar static styles i la funció css per donar als components de TaskFlow una aparença encapsulada i coherent, i on recuperaràs la reflexió d'atributs vista en aquesta lliçó per poder aplicar estils diferents segons l'estat o la urgència de cada targeta mitjançant selectors com :host([estado="hecha"]).

Errors Comuns i Consells

  • Esperar que canviar una propietat des de JavaScript actualitzi l'atribut sense reflect: true: com es va explicar a l'apartat 2, la sincronització per defecte va només d'atribut a propietat; si cal el camí invers, s'ha d'activar explícitament amb reflect: true, i només a les propietats on de veritat aporti valor.
  • Activar reflect: true a totes les propietats sense necessitat: com es va apuntar a l'apartat 3, reflectir afegeix una crida a setAttribute a cada actualització; en propietats que mai s'utilitzaran com a selector CSS ni s'inspeccionaran des de fora, aquesta sincronització addicional és feina malbaratada.
  • Confondre la sintaxi de punt (.propiedad) amb un atribut normal: com es va recordar a l'apartat 6, .titulo="${tarea.titulo}" assigna directament la propietat JavaScript, sense passar per text ni per cap conversor d'atribut; és la manera recomanada de passar dades de tipus no textuals (números, booleans, arrays, objectes) d'un component pare a un component fill.
  • Passar un array complet de tasques mutant l'array existent en lloc de reassignar-lo: si més endavant s'afegeix una tasca a <task-list> amb this.tareas.push(nuevaTarea) en lloc de this.tareas = [...this.tareas, nuevaTarea], la propietat reactiva tareas no detectarà el canvi, exactament pel motiu explicat a la primera lliçó d'aquest mòdul sobre mutació enfront de reassignació.

Exercicis

  1. Afegeix una quarta tasca a l'array inicial de <task-list>, amb els seus propis titulo, estado, prioridad i urgente, i comprova que la quarta <task-card> generada mostra correctament les seves pròpies dades, diferents de les altres tres.
  2. Declara a <task-card> la propietat estado amb reflect: true (a més de type: String, que ja tenia) i comprova, inspeccionant l'element amb les eines de desenvolupament del navegador després d'assignar elemento.estado = 'hecha' des de la consola, que l'atribut estado del DOM també canvia a "hecha".
  3. Explica amb les teves pròpies paraules, recolzant-te en l'apartat 4, per què reflectir la propietat estado (amb reflect: true) té més sentit pràctic a TaskFlow que reflectir, per exemple, la propietat fechaLimite de tipus data.

Solucions

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

En tornar a carregar la pàgina, Array.map genera una quarta plantilla <task-card> amb les seves pròpies propietats .titulo, .estado, .prioridad i .urgente interpolades a partir d'aquest nou objecte, mostrant dades diferents de les tres targetes anteriors, sense tocar la resta de render().

static properties = {
  titulo: { type: String },
  estado: { type: String, reflect: true },
  prioridad: { type: Number },
  urgente: { type: Boolean },
  expandida: { state: true },
  fechaLimite: { converter: conversorDeFecha, attribute: 'fecha-limite' },
};

Després d'executar elemento.estado = 'hecha' a la consola, en inspeccionar l'element a les eines de desenvolupament s'observa que l'etiqueta passa a mostrar <task-card estado="hecha" ...>, perquè reflect: true fa que Lit cridi automàticament setAttribute('estado', 'hecha') sobre l'element cada vegada que la propietat canvia.

  1. Reflectir estado té sentit pràctic perquè, com s'explica a l'apartat 4, permet escriure selectors CSS basats en atribut (:host([estado="hecha"])) per donar un estil visual diferent a les targetes segons el seu estat, una cosa que es desenvoluparà al mòdul 4, i també permet localitzar targetes pel seu estat des de fora amb selectors com task-card[estado="hecha"]. fechaLimite, en canvi, és un valor gairebé continu (una data concreta, potencialment diferent en cada tasca) que rarament té sentit utilitzar com a selector CSS o com a filtre per igualtat exacta d'atribut; a més, el seu conversor personalitzat ja transforma la data a text d'una manera pensada per a la lectura per part de Lit, no necessàriament per a un ús còmode com a selector, per la qual cosa reflectir-la aportaria poc valor pràctic davant del cost addicional de sincronitzar-la a cada actualització.

Conclusió

En aquesta última lliçó del mòdul 3 has acabat de fixar la distinció entre atribut (text, a l'HTML) i propietat (qualsevol tipus, a JavaScript), entenent que Lit sincronitza per defecte només d'atribut a propietat, i que reflect: true activa també el camí invers per als casos en què interessa —típicament, per poder aplicar estils CSS segons el valor d'una propietat, com s'explotarà al mòdul 4. Amb aquesta base, has convertit tareas, a <task-list>, en una propietat reactiva de tipus Array, i has completat la plantilla de <task-list> per passar les dades de cada tasca a la seva <task-card> corresponent mitjançant la sintaxi de propietats amb punt, aconseguint que cada targeta del tauler mostri per fi la informació de la seva pròpia tasca.

Amb això es tanca el mòdul "Propietats i Estat Reactiu": <task-card> i <task-list> ja són components reactius de veritat, amb dades pròpies, estat intern i una comunicació de pare a fill ben resolta. Ara que les dades són reactives i cada targeta mostra la informació correcta, toca ocupar-se de com es veuen: al mòdul 4, "Estils en Components Lit", aprendràs a donar estil visual als components de TaskFlow.

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