A la lliçó anterior vas crear el teu primer component Lit sense aturar-te massa en el perquè de cada peça. Abans d'avançar cap a les plantilles reactives, convé fer una pausa i entendre amb detall l'anatomia completa d'una classe que estén LitElement: quines parts la componen, com es relaciona amb l'estàndard de Custom Elements que ja coneixes, què implica que usi Shadow DOM per defecte, i quins moments de vida travessa un component des que neix fins que desapareix. Aquesta lliçó és l'última teòrica del mòdul i tanca el cercle obert a la primera lliçó, deixant el terreny preparat per al mòdul 2, on començaràs a treballar amb plantilles realment dinàmiques.

Contingut

  1. Estructura general d'una classe LitElement
  2. Declarar propietats: decoradors davant de static properties
  3. LitElement és HTMLElement: la relació amb l'estàndard
  4. Shadow DOM per defecte: què significa i què implica
  5. Panorama del cicle de vida d'un component Lit
  6. Cap al mòdul 2: plantilles reactives

  1. Estructura general d'una classe LitElement

Tota classe que estén LitElement pot contenir, en general, els blocs següents (cap és obligatori excepte render(), que és l'únic imprescindible perquè el component mostri alguna cosa):

import { LitElement, html, css } from 'lit';

class TaskCard extends LitElement {

  // 1. Declaració de propietats reactives (es detalla al mòdul 3)
  static properties = {
    titulo: { type: String },
  };

  // 2. Estils encapsulats (es detallen al mòdul 4)
  static styles = css`
    :host {
      display: block;
      border: 1px solid #ccc;
    }
  `;

  // 3. Constructor: inicialització de l'estat intern
  constructor() {
    super();
    this.titulo = 'Tarea sin título';
  }

  // 4. Callbacks del cicle de vida (es detallen al mòdul 6)
  connectedCallback() {
    super.connectedCallback();
  }

  // 5. Plantilla del component (obligatori)
  render() {
    return html`<h3>${this.titulo}</h3>`;
  }
}

customElements.define('task-card', TaskCard);

No et preocupis si algunes d'aquestes peces (static properties, static styles, connectedCallback) encara no tenen gaire sentit: s'explicaran amb profunditat als mòduls 3, 4 i 6 respectivament. L'objectiu d'aquest repàs és que, en arribar a aquests mòduls, ja tinguis clara la "forma" general de la classe i sàpigues ubicar cada peça nova dins d'un esquema conegut. De fet, fixa't que el component task-card.js de la lliçó anterior era, simplement, una versió d'aquesta mateixa estructura en la qual només s'omplia el bloc 5 (render()); tots els altres blocs són opcionals i s'afegeixen segons les necessitats del component.

Un detall sobre el constructor: si es defineix, és obligatori cridar primer a super(), com en qualsevol classe de JavaScript que hereta d'una altra. És el lloc habitual per inicialitzar valors per defecte de les propietats del component, cosa que es reprendrà amb més detall al mòdul 3.

  1. Declarar propietats: decoradors davant de static properties

A la lliçó anterior ja vas veure que existeixen dos estils per registrar un component: customElements.define i el decorador @customElement. Aquesta mateixa dualitat d'estils es repeteix per declarar les propietats reactives d'un component (el seu estudi complet correspon al mòdul 3, però convé reconèixer ja la sintaxi, perquè apareixerà en exemples d'aquest curs i a la documentació oficial de Lit).

Amb decoradors (requereix suport de compilació, com es va explicar a la lliçó de configuració de l'entorn):

import { LitElement, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';

@customElement('task-card')
class TaskCard extends LitElement {
  @property({ type: String })
  titulo = 'Tarea sin título';

  render() {
    return html`<h3>${this.titulo}</h3>`;
  }
}

Amb static properties, en JavaScript pla sense cap pas de compilació addicional:

import { LitElement, html } from 'lit';

class TaskCard extends LitElement {
  static properties = {
    titulo: { type: String },
  };

  constructor() {
    super();
    this.titulo = 'Tarea sin título';
  }

  render() {
    return html`<h3>${this.titulo}</h3>`;
  }
}

customElements.define('task-card', TaskCard);
Aspecte Amb decoradors (@property) Amb static properties
Requereix compilació addicional Sí (TypeScript o Babel amb plugin de decoradors) No, JavaScript estàndard
On es declara el valor per defecte Junt a la mateixa declaració de la propietat Al constructor, després de super()
Verbositat Més compacta Lleugerament més extensa
Resultat en temps d'execució Idèntic Idèntic

Tots dos estils produeixen exactament el mateix comportament; la tria és una qüestió de preferència i de les eines disponibles al projecte. Aquest curs, com ja es va explicar a la lliçó de configuració de l'entorn, usarà principalment static properties, mostrant la variant amb decoradors com a referència quan resulti útil per a qui treballi amb TypeScript en els seus propis projectes.

  1. LitElement és HTMLElement: la relació amb l'estàndard

És fonamental interioritzar una idea que ja es va apuntar a la primera lliçó del mòdul: LitElement no substitueix l'estàndard de Custom Elements, l'estén. En termes de JavaScript, la cadena d'herència és:

HTMLElement  →  ReactiveElement  →  LitElement  →  TaskCard (el teu component)
  • HTMLElement és la classe base de la qual hereten tots els elements del DOM, tant els natius del navegador (HTMLButtonElement, HTMLDivElement...) com qualsevol Custom Element.
  • ReactiveElement és una classe intermèdia que Lit afegeix per incorporar el sistema de propietats reactives i el cicle d'actualització, sense encara imposar un motor concret de plantilles.
  • LitElement hereta de ReactiveElement i afegeix el mètode render() juntament amb l'ús de la funció html com a motor de plantilles.
  • La teva classe (TaskCard, en l'exemple) hereta de LitElement i hereta, per tant, totes les capacitats de les tres classes anteriors.

La conseqüència pràctica d'aquesta cadena d'herència és que un component Lit és, literalment, un HTMLElement. Això significa que hereta tota l'API estàndard d'un element del DOM: se li poden afegir i treure atributs (setAttribute, getAttribute), se li poden escoltar i despatxar esdeveniments (addEventListener, dispatchEvent, cosa que s'explota a fons al mòdul d'esdeveniments), té accés a classList, i participa a l'arbre del DOM exactament igual que qualsevol altre element. Res d'això és una simulació ni una capa a part: és la mateixa plataforma web.

  1. Shadow DOM per defecte: què significa i què implica

A la lliçó anterior, en inspeccionar <task-card> a les eines de desenvolupament, va aparèixer un node #shadow-root que potser va resultar sorprenent. Això passa perquè LitElement, per defecte, crida internament a attachShadow({ mode: 'open' }) sobre cada instància del component i renderitza el resultat de render() dins d'aquest Shadow DOM, no directament com a fills de l'element al DOM normal.

// Això és, de manera simplificada, el que fa LitElement per tu
// la primera vegada que el component necessita renderitzar-se:
const raizSombra = this.attachShadow({ mode: 'open' });
raizSombra.innerHTML = /* resultat de render() */;

Què implica això a la pràctica, sense entrar encara en el detall dels estils (que correspon al mòdul 4)?

  • Encapsulació de l'HTML intern: el contingut retornat per render() no és visible mitjançant document.querySelector des de fora del component; cal accedir primer a elemento.shadowRoot i buscar dins seu.
  • Encapsulació dels estils: qualsevol CSS que es declari dins del component (amb static styles, com es va veure a l'apartat 1) no es filtra cap a la resta de la pàgina, i els estils globals de la pàgina tampoc no entren per accident al component. Això evita un dels problemes més habituals en construir interfícies grans: col·lisions de noms de classes CSS entre components diferents.
  • Contingut distribuït amb <slot>: el Shadow DOM també defineix un mecanisme, <slot>, per permetre que un component rebi contingut HTML "de fora" i el posicioni dins de la seva pròpia estructura interna. Aquest mecanisme s'explorarà amb detall al mòdul d'estils.

Per ara, el punt clau que cal retenir és aquest: cada component Lit viu, per defecte, en la seva pròpia bombolla aïllada de la resta de la pàgina, tant en estructura com en estils. Aquesta és una de les raons per les quals Lit resulta especialment adequat per construir biblioteques de components reutilitzables com els de TaskFlow: es poden combinar <task-card>, <task-list> i <user-avatar> sense por a que els estils d'un interfereixin amb els d'un altre.

  1. Panorama del cicle de vida d'un component Lit

Un component Lit, igual que qualsevol Custom Element, no existeix de manera estàtica: neix, s'actualitza possiblement moltes vegades, i en algun moment pot desaparèixer del DOM. Lit ofereix una sèrie de mètodes ("callbacks del cicle de vida") que s'executen automàticament en diferents moments d'aquesta vida, i que un component pot sobreescriure per executar lògica pròpia en el moment adequat.

En aquesta lliçó només es presenta un mapa general, amb el nom de cada callback i per a què serveix a grans trets; el funcionament detallat, amb exemples d'ús real, s'estudia amb profunditat al mòdul 6 ("Cicle de Vida i Comportament Avançat").

Moment del cicle de vida Callback Per a què serveix, a grans trets
L'element s'insereix al DOM connectedCallback() Heretat directament de l'estàndard de Custom Elements; bon lloc per subscriure's a esdeveniments externs o iniciar temporitzadors
L'element es retira del DOM disconnectedCallback() També de l'estàndard; lloc habitual per "netejar" allò que es va iniciar a connectedCallback (cancel·lar subscripcions, temporitzadors...)
Abans d'aplicar una actualització willUpdate(changedProperties) Propi de Lit; permet calcular valors derivats just abans de renderitzar, en funció de quines propietats han canviat
Es genera la nova plantilla render() L'únic obligatori; retorna l'HTML actual del component
Després d'aplicar una actualització al DOM updated(changedProperties) Propi de Lit; útil per reaccionar a canvis ja reflectits al DOM real
Just després de la primera actualització firstUpdated(changedProperties) Propi de Lit; s'executa una única vegada, ideal per a inicialitzacions que necessiten que el DOM intern ja existeixi

Dues idees importants a retenir, encara que el detall arribi més endavant:

  • Els callbacks connectedCallback i disconnectedCallback no són una invenció de Lit: pertanyen a l'estàndard de Custom Elements vist a la primera lliçó del mòdul. Lit els aprofita i afegeix la seva pròpia capa de callbacks addicionals (willUpdate, updated, firstUpdated) específics del seu sistema de renderitzat reactiu.
  • Cap component necessita implementar tots aquests callbacks. La majoria dels components senzills, com el <task-card> estàtic de la lliçó anterior, no necessiten cap d'ells; es van incorporant a mesura que el component necessita reaccionar a moments concrets de la seva vida.

  1. Cap al mòdul 2: plantilles reactives

Amb aquesta lliçó es tanca el mòdul d'introducció. Ja coneixes la base sobre la qual es recolza tota la resta: què són els Web Components com a estàndard, què aporta Lit sobre ells, com configurar un entorn de treball, com es veu per dins una classe LitElement, i quins moments de vida travessa un component.

Tot i això, tots els exemples vistos fins ara comparteixen una limitació deliberada: les seves plantilles són completament estàtiques. El <task-card> que has construït sempre mostra la mateixa tasca d'exemple, sense importar què passi a l'aplicació. Aquesta limitació és exactament el punt de partida del mòdul 2, "Plantilles Reactives i Renderitzat", on aprendràs a inserir expressions dinàmiques dins de la funció html, a mostrar o ocultar contingut segons condicions, a renderitzar llistes completes de tasques, i a entendre com i quan decideix Lit tornar a executar render().

Errors Comuns i Consells

  • Intentar accedir al contingut intern del component amb document.querySelector des de fora: com que el contingut viu dins d'un Shadow DOM, document.querySelector('task-card h3') no trobarà res. Cal accedir primer al propi element i després al seu shadowRoot (elemento.shadowRoot.querySelector('h3')), o millor encara, evitar manipular el DOM intern manualment i deixar que sigui render() qui decideixi què es mostra.
  • Oblidar cridar a super() al constructor: si es defineix un constructor personalitzat sense invocar primer a super(), el navegador llançarà un error en temps d'execució. Aquesta regla no és específica de Lit, sinó de qualsevol classe de JavaScript que hereti d'una altra.
  • Pensar que cal implementar tots els callbacks del cicle de vida: com s'ha vist a la taula de l'apartat 5, la majoria dels components només necessiten render(). Afegir callbacks "per si de cas" complica el component sense necessitat.
  • Confondre ReactiveElement amb LitElement: en el dia a dia gairebé mai s'interactua directament amb ReactiveElement; es menciona aquí només per entendre la cadena d'herència. A la pràctica, tots els components d'aquest curs estendran LitElement directament.

Exercicis

  1. Dibuixa (en un paper o en un fitxer de text) la cadena d'herència completa d'un component UserAvatar que definiràs tu mateix, des de HTMLElement fins a la teva classe, indicant què aporta cada esglaó de la cadena.
  2. Recupera el component task-card.js de la lliçó anterior (la versió estàtica) i afegeix un mètode connectedCallback() que, mitjançant console.log, escrigui a la consola del navegador el missatge "task-card conectado al DOM". Recorda cridar a super.connectedCallback().
  3. Explica amb les teves pròpies paraules, en dues o tres frases, per què el fet que LitElement usi Shadow DOM per defecte és un avantatge per a un projecte com TaskFlow, que tindrà diversos components diferents convivint a la mateixa pàgina.

Solucions

  1. La cadena, igual que la de TaskCard vista a l'apartat 3, és:
HTMLElement (estàndard del navegador: API base de qualsevol element del DOM)
  → ReactiveElement (Lit: sistema de propietats reactives i cicle d'actualització)
    → LitElement (Lit: motor de plantilles mitjançant render() i html)
      → UserAvatar (la teva classe: la lògica i plantilla concretes de l'avatar)
import { LitElement, html } from 'lit';

class TaskCard extends LitElement {
  connectedCallback() {
    super.connectedCallback();
    console.log('task-card conectado al DOM');
  }

  render() {
    return html`
      <article>
        <h3>Preparar la demo del sprint</h3>
        <p>Estado: En curso</p>
        <p>Asignada a: Ana</p>
      </article>
    `;
  }
}

customElements.define('task-card', TaskCard);

En recarregar la pàgina amb les eines de desenvolupament obertes, hauria d'aparèixer el missatge a la consola per cada <task-card> present a l'HTML.

  1. Una possible resposta: en estar cada component encapsulat en el seu propi Shadow DOM, els estils CSS de <task-card>, <task-list> o <user-avatar> no poden interferir entre si ni amb els estils globals de la pàgina, encara que usin els mateixos noms de classe o d'etiqueta. Això permet desenvolupar i mantenir cada component de manera independent, sense necessitat de coordinar convencions de noms CSS entre tot l'equip, cosa especialment valuosa a mesura que TaskFlow vagi sumant més components al llarg del curs.

Conclusió

En aquesta lliçó has acabat de desmuntar l'anatomia d'un component Lit: la seva estructura general amb blocs opcionals i un únic mètode obligatori (render()), les dues formes de declarar propietats (decoradors i static properties), la seva relació directa amb HTMLElement a través de la cadena d'herència HTMLElement → ReactiveElement → LitElement, l'ús del Shadow DOM per defecte per encapsular estructura i estils, i una primera panoràmica —només els noms i el seu propòsit general— del cicle de vida que es detallarà al mòdul 6.

Amb això es tanca el mòdul d'introducció. Al mòdul 2, "Plantilles Reactives i Renderitzat", deixaràs enrere els components estàtics: aprendràs a inserir expressions dinàmiques a la funció html, a renderitzar condicionalment contingut, a mostrar llistes d'elements i a entendre en profunditat el cicle de renderitzat de Lit, donant a <task-card>, <task-list> i la resta de peces de TaskFlow la seva primera capa real de vida dinàmica.

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