Les tres lliçons anteriors han cobert proves, accessibilitat i rendiment, cadascuna com un angle de qualitat transversal aplicat sobre TaskFlow tal com va quedar al final del mòdul 8. Aquesta darrera lliçó del mòdul tanca amb una síntesi diferent: en lloc d'un angle nou, recorre de principi a fi les decisions de disseny ja preses durant el curs, les agrupa en parelles de patró recomanat enfront d'antipatró, i explica per què cada antipatró, encara que a vegades "funcioni" a curt termini, acaba costant més d'allò que estalvia. És, en certa manera, un repàs del curs complet vist des del revers: no què fa cada peça de Lit, sinó quin error concret s'evita en utilitzar-la bé.

Contingut

  1. Per què convé una lliçó de síntesi abans del projecte final
  2. Taula general: patrons enfront d'antipatrons del curs
  3. Immutabilitat enfront de mutar arrays i objectes al lloc
  4. composed: true enfront d'esdeveniments personalitzats que no surten del Shadow DOM
  5. Plantilles declaratives enfront de lògica de negoci dins de render()
  6. Components descompostos enfront de components que acumulen responsabilitats
  7. Neteja de recursos enfront de fuites a disconnectedCallback i controladors
  8. Un criteri final: preguntes que detecten un antipatró abans d'escriure'l
  9. Cap al projecte final: repassar TaskFlow de principi a fi

  1. Per què convé una lliçó de síntesi abans del projecte final

Cada antipatró d'aquesta lliçó ja va aparèixer, en algun moment del curs, assenyalat com a "error comú" al final d'una lliçó concreta, en el context estret de la tècnica que s'estava explicant en aquell moment. Allò que aquesta forma de presentar-los no podia mostrar és el patró que es repeteix entre mòduls diferents: que oblidar composed: true (mòdul 5) i no netejar un setInterval a disconnectedCallback (mòdul 6) són, en el fons, la mateixa classe d'error —oblidar la meitat d'una operació que exigeix simetria—, encara que apareguin en dues lliçons separades per diversos mòduls. Veure'ls junts, ara que ja es coneix el curs complet, ajuda a reconèixer-los amb més facilitat en codi nou, inclòs el que s'escriurà al mòdul 10.

  1. Taula general: patrons enfront d'antipatrons del curs

# Antipatró Patró recomanat On es va explicar
1 Mutar un array o un objecte al lloc dins d'una propietat reactiva Substituir la propietat per una còpia nova (map, filter, {...obj}) Lliçó "Comunicació de Pare a Fill amb Propietats" (mòdul 5)
2 Despatxar un esdeveniment personalitzat sense composed: true des d'un component amb Shadow DOM bubbles: true i composed: true junts, perquè l'esdeveniment surti del shadow root Lliçó "Esdeveniments Personalitzats: Comunicació de Fill a Pare" (mòdul 5)
3 Ficar lògica de negoci o càlculs costosos directament dins de render() Derivar aquest càlcul a willUpdate, o extraure'l a un mètode/controlador a part Lliçons "Hooks Reactius" (mòdul 6) i "Rendiment i Optimització" (mòdul 9)
4 Un únic component que acumula massa responsabilitats sense descompondre's Extreure subcomponents amb una responsabilitat clara, com <user-avatar> o <task-filter> Lliçons "Slots i Estilització de Contingut Distribuït" (mòdul 4) i "Context Compartit amb @lit/context" (mòdul 7)
5 No alliberar recursos (temporitzadors, subscripcions) a disconnectedCallback o a hostDisconnected Netejar en el mateix lloc simètric on es va arrencar el recurs Lliçons "Callbacks del Cicle de Vida" i "Controladors Reactius" (mòdul 6)

Les cinc files comparteixen una mateixa estructura de fons, encara que a primera vista semblin problemes diferents: cada antipatró "estalvia" una línia o un pas en el moment d'escriure'l, i cada patró recomanat inverteix aquest petit cost inicial a canvi d'un comportament predictible més endavant, quan el component creix, es reutilitza en un context diferent, o convivi amb altres que no controla directament.

  1. Immutabilitat enfront de mutar arrays i objectes al lloc

La lliçó "Comunicació de Pare a Fill amb Propietats" va explicar, amb la taula d'operacions de la lliçó 05-03, per què this.tareas.push(...) o this.tareas[0].estado = 'hecha' no disparen cap actualització visible a Lit: la comparació per defecte d'una propietat reactiva de tipus objecte o array és per referència, no per contingut, i una mutació al lloc mai canvia aquesta referència.

// Antipatró: muta l'array existent
gestionarTareaCambiada(idTarea, event) {
  const tarea = this.tareas.find((t) => t.id === idTarea);
  tarea.estado = event.detail.nuevoEstado;
  this.requestUpdate(); // pedaç que amaga el problema real
}

// Patró recomanat: substitueix amb una còpia nova
gestionarTareaCambiada(idTarea, event) {
  const nuevoEstado = event.detail.nuevoEstado;
  this.tareas = this.tareas.map((tarea) =>
    tarea.id === idTarea ? { ...tarea, estado: nuevoEstado } : tarea
  );
}

El this.requestUpdate() forçat a la primera versió no és una correcció, sinó un símptoma: "funciona" perquè obliga Lit a tornar a renderitzar sense importar si va detectar o no un canvi real, però no resol el problema de fons, i qualsevol altre codi que depengués de comparar l'array anterior amb l'actual (una eina de depuració amb historial, o una futura funcionalitat de "desfer") continuaria fallant, perquè els objectes mutats al lloc no deixen cap rastre de quin era el seu valor anterior. La versió immutable no necessita cap requestUpdate() manual perquè la pròpia assignació this.tareas = ... ja canvia la referència, que és justament el que el sistema de reactivitat de Lit necessita detectar per si mateix.

  1. composed: true enfront d'esdeveniments personalitzats que no surten del Shadow DOM

La lliçó "Esdeveniments Personalitzats: Comunicació de Fill a Pare" va assenyalar aquest error com el més freqüent en depurar comunicació entre components, i mereix repetir-se aquí junt amb la resta perquè comparteix la mateixa naturalesa que l'antipatró de l'apartat anterior: una operació que sembla completa, però li falta una segona meitat imprescindible.

// Antipatró: l'esdeveniment mai surt del shadow root de <task-card>
this.dispatchEvent(new CustomEvent('tarea-cambiada', { detail: { nuevoEstado } }));

// Patró recomanat: bubbles i composed junts
this.dispatchEvent(
  new CustomEvent('tarea-cambiada', {
    detail: { nuevoEstado },
    bubbles: true,
    composed: true,
  })
);

Sense composed: true, el codi de la primera versió no llança cap error ni cap advertència: l'esdeveniment es crea, es despatxa, i qualsevol console.log col·locat dins del propi mètode confirmaria, enganyosament, que "tot funciona". La fallada només es manifesta un pas més enllà, a <task-list>, que mai arriba a rebre l'esdeveniment perquè aquest queda atrapat dins del shadow root de <task-card>, exactament la classe de fallada silenciosa —sense missatge d'error, sense traça a la consola— que resulta més costosa de diagnosticar que un error explícit.

  1. Plantilles declaratives enfront de lògica de negoci dins de render()

La lliçó "Rendiment i Optimització" (09-03) ja va explicar el cost de recalcular dins de render() alguna cosa que es podria derivar una sola vegada a willUpdate; aquest apartat recupera la mateixa idea des d'un angle diferent, el de la claredat del propi codi, no només el seu cost:

// Antipatró: decideix, calcula i formata tot dins de render()
render() {
  const diasRestantes = Math.floor((this.fechaLimite - Date.now()) / 86400000);
  const claseUrgencia = diasRestantes < 1 ? 'critico' : diasRestantes < 3 ? 'aviso' : 'normal';
  return html`<p class="${claseUrgencia}">${diasRestantes} días restantes</p>`;
}

// Patró recomanat: render() només tradueix dades ja preparades a HTML
willUpdate(changedProperties) {
  if (changedProperties.has('fechaLimite')) {
    this._diasRestantes = Math.floor((this.fechaLimite - Date.now()) / 86400000);
    this._claseUrgencia = this._diasRestantes < 1 ? 'critico' : this._diasRestantes < 3 ? 'aviso' : 'normal';
  }
}

render() {
  return html`<p class="${this._claseUrgencia}">${this._diasRestantes} días restantes</p>`;
}

Més enllà del cost de recalcular a cada renderització (ja cobert a la lliçó anterior), la primera versió mescla dues responsabilitats diferents dins d'un mateix mètode: decidir què significa la dada (quants dies queden, i a partir de quants dies alguna cosa es considera "crítica"?) i decidir com mostrar-la (quina etiqueta HTML, quina classe CSS). La segona versió separa totes dues preguntes en dos llocs diferents del cicle de vida, cadascun amb una responsabilitat clara: willUpdate decideix el significat, render() decideix la representació. Aquesta separació, encara que sembli un simple reordenament del mateix codi, facilita raonar sobre cada meitat per separat —i, no per casualitat, és exactament la mateixa separació que ja es va explicar, amb un altre exemple, a la lliçó "Hooks Reactius" del mòdul 6.

  1. Components descompostos enfront de components que acumulen responsabilitats

<task-card>, al llarg del curs, podria haver crescut com una única classe monolítica que dibuixés directament, dins del seu propi render(), tant l'avatar de la persona assignada com el filtre de cerca de tota l'aplicació. En el seu lloc, el curs ha anat extraient, en el moment en que cada responsabilitat es tornava suficientment diferent, un component propi: <user-avatar> al mòdul 4, quan mostrar una persona assignada (amb imatge o inicials, amb el seu propi <slot> i les seves pròpies regles d'estil) va deixar de ser un simple detall visual de <task-card> i va passar a merèixer la seva pròpia classe reutilitzable; <task-filter> al mòdul 7, quan gestionar el filtre de cerca va deixar de ser alguna cosa que <task-list> pogués continuar absorbint sense mesclar dues responsabilitats alienes entre elles (mostrar tasques, d'una banda; decidir quines mostrar, de l'altra).

Senyal de que convé extreure un component Exemple ja aplicat a TaskFlow
Una peça de la plantilla té el seu propi cicle d'estils, independent de la resta <user-avatar>, amb la seva pròpia variable CSS --tamano-avatar
Una peça de comportament es podria reutilitzar en un context diferent de l'actual <user-avatar>, pensat per poder aparèixer en qualsevol lloc de TaskFlow que necessiti mostrar una persona, no només dins de <task-card>
Dues responsabilitats dins de la mateixa classe canvien per motius diferents i en moments diferents <task-filter> canvia quan canvien les regles de filtratge; <task-list> canvia quan canvia com es mostren les tasques
El nom d'una classe comença a necessitar la conjunció "i" per descriure's ("la llista que també filtra i també pagina") Evitat extraient <task-filter> en lloc d'absorbir el filtre dins de <task-list>

L'antipatró simètric a evitar és el contrari: extreure un component nou per cada detall mínim, sense que existeixi cap de les senyals de la taula anterior, multiplicant el nombre de fitxers i de Shadow DOMs sense cap guany real de claredat ni de reutilització. El criteri, igual que amb la resta d'aquesta lliçó, no és "descompondre sempre" ni "descompondre mai", sinó reconèixer les senyals concretes que justifiquen l'extracció, tal com TaskFlow ho ha fet en tots dos casos de la taula.

  1. Neteja de recursos enfront de fuites a disconnectedCallback i controladors

L'últim parell d'aquesta lliçó connecta directament els mòduls 6 i 9: la lliçó "Callbacks del Cicle de Vida" va advertir sobre no netejar un setInterval a disconnectedCallback, i la lliçó "Controladors Reactius" va repetir la mateixa advertència, ara sobre hostDisconnected, assenyalant que el risc es multiplica per cada host que utilitzi el controlador.

// Antipatró: arrenca el temporitzador, mai el detén
connectedCallback() {
  super.connectedCallback();
  this._idIntervalo = setInterval(() => this._comprobar(), 60000);
}
// (sense disconnectedCallback, o amb un que no crida clearInterval)

// Patró recomanat: simetria entre connexió i desconnexió
connectedCallback() {
  super.connectedCallback();
  this._idIntervalo = setInterval(() => this._comprobar(), 60000);
}

disconnectedCallback() {
  clearInterval(this._idIntervalo);
  super.disconnectedCallback();
}

Una <task-card> que s'elimina del DOM (per exemple, perquè el filtre de <task-filter> l'exclou del resultat) sense que el seu temporitzador s'aturi continua executant _comprobar() cada minut, indefinidament, encara que ja no existeixi cap node visible al qual aquesta comprovació pugui afectar; amb centenars de targetes creades i destruïdes al llarg d'una sessió llarga d'ús (l'escenari d'una llista gran explorat a la lliçó anterior), aquests temporitzadors orfes s'acumulen l'un darrere l'altre, consumint memòria i cicles de procés sense cap benefici, un problema que només es manifesta amb el temps i que rarament apareix en una prova manual ràpida de cinc minuts, precisament el tipus de fallada que una bateria de proves automatitzades com la de la lliçó 09-01 tampoc detecta si no es dissenya específicament per comprovar-ho.

  1. Un criteri final: preguntes que detecten un antipatró abans d'escriure'l

Els cinc parells anteriors comparteixen un tret comú que convé convertir en hàbit, no només en una llista per consultar després d'escriure el codi: cada antipatró correspon a una pregunta concreta que, formulada a temps, l'hauria evitat.

Pregunta que convé fer-se Antipatró que detecta
Estic substituint aquesta propietat per un valor nou, o modificant el valor que ja tenia? Mutació al lloc (apartat 3)
Si aquest component té Shadow DOM, aquest esdeveniment necessita sortir-ne? Falta de composed: true (apartat 4)
Aquest càlcul depèn només de propietats que ja han canviat, o es repeteix sense condició? Lògica de negoci a render() (apartat 5)
Aquesta classe canvia per més d'un motiu diferent? Responsabilitats sense descompondre (apartat 6)
Per cada recurs que arrenco, on exactament el vaig a aturar? Fuita de recursos (apartat 7)

  1. Cap al projecte final: repassar TaskFlow de principi a fi

Amb aquesta síntesi, el mòdul 9 completa la cobertura de qualitat que TaskFlow necessitava abans de considerar-se una aplicació acabada: proves automatitzades que verifiquen el seu comportament sense dependre de comprovacions manuals, accessibilitat per a qui no utilitza un ratolí sobre una pantalla convencional, rendiment revisat amb criteri enfront de volums de dades reals, i ara un catàleg de patrons i antipatrons que resumeix, d'una ullada, les decisions de disseny dels vuit mòduls anteriors.

Allò que queda pendent no és cap concepte nou de Lit —el curs ja ha cobert plantilles, reactivitat, estils, esdeveniments, cicle de vida, directives, context, integració, proves i bones pràctiques—, sinó recórrer TaskFlow de principi a fi com a aplicació completa: repassar cada component construït al llarg del curs, identificar quines peces van quedar mencionades però no completament resoltes, i acabar d'ensamblar-les en la versió final i acabada de l'aplicació. Aquesta és, precisament, la tasca de l'última lliçó del curs, "Projecte: Construint TaskFlow", el mòdul de tancament que dona per acabat el recorregut complet des del primer component Lit de la lliçó 01-03 fins a l'aplicació final.

Errors Comuns i Consells

  • Tractar aquesta lliçó com una llista de regles aïllades, en lloc d'un criteri transferible: els cinc parells de l'apartat 2 no són una llista tancada d'errors específics de TaskFlow, sinó exemples concrets d'un grapat de preguntes generals (apartat 8) que es poden aplicar a qualsevol component Lit futur, inclòs qualsevol que s'afegeixi al projecte final.
  • Aplicar un patró recomanat sense entendre quin antipatró evita: copiar bubbles: true, composed: true en tots els esdeveniments "per costum", sense saber que resol el problema concret de travessar el Shadow DOM, deixa a qui escriu el codi sense criteri per decidir quan, alguna vegada, cap de les dues opcions faria falta (un esdeveniment que un component només necessita escoltar sobre si mateix, sense cap pare interessat).
  • Confondre "descompondre en components" amb una regla numèrica: com s'ha assenyalat a l'apartat 6, no existeix un nombre correcte de components per aplicació; les senyals de la taula de l'apartat 6, no un recompte arbitrari, són el criteri que hauria de decidir quan extreure una peça nova.
  • Revisar els antipatrons només al final d'un projecte, no durant el seu desenvolupament: aquesta lliçó arriba, deliberadament, abans del projecte final, no després; l'objectiu és que les preguntes de l'apartat 8 es converteixin en hàbit mentre es construeix el mòdul 10, no en una llista de comprovació aplicada a corre-cuita un cop acabat.

Exercicis

  1. Repassa el render() de <task-board> tal com va quedar després de la lliçó "Mixins i Composició de Comportament" del mòdul 6 (amb ConEstadoCarga) i assenyala, amb el criteri de l'apartat 5 d'aquesta lliçó, si this.conIndicadorDeCarga(...) compta com "lògica de negoci dins de render()" o si encaixa millor amb el patró recomanat. Justifica la resposta.
  2. Un company d'equip, revisant <task-filter>, troba que manejarTexto i manejarEstado criden this.valorActual.actualizar({...}), que al seu torn reassigna this._filtroProvider.value a <task-board> amb l'operador de propagació ({ ...this._filtroProvider.value, ...cambios }, vist a la lliçó "Context Compartit amb @lit/context"). Explica, amb el criteri de l'apartat 3, per què aquesta reassignació compta com el patró recomanat i no com l'antipatró de mutació al lloc.
  3. Aplicant les cinc preguntes de l'apartat 8, identifica quina d'elles hauria assenyalat el problema si ContadorTiempoRestanteController (mòdul 6) hagués assignat directament this.host.cercaDeVencer = ... en lloc de mantenir el seu propi camp cercaDeVencer i cridar this.host.requestUpdate(), tal com es va advertir als "Errors Comuns" de la lliçó "Controladors Reactius".

Solucions

  1. this.conIndicadorDeCarga(...) no és lògica de negoci dins de render() en el sentit assenyalat per l'apartat 5: no calcula ni deriva cap dada nova a partir de propietats (no decideix "què significa" cargando, aquest valor ja arriba calculat com una propietat booleana simple); es limita a traduir un valor ja existent (this.cargando) en una de dues plantilles alternatives, exactament la responsabilitat que l'apartat 5 reserva per a render(). Si conIndicadorDeCarga calculés, en canvi, alguna cosa com "quant temps porta carregant" a partir d'una marca de temps, aquest càlcul sí que encaixaria millor a willUpdate, però no és el que fa en la seva forma actual.
  2. La reassignació this._filtroProvider.value = { ...this._filtroProvider.value, ...cambios } segueix exactament el patró recomanat de l'apartat 3: en lloc de modificar l'objecte de context existent afegint-li o canviant-li una propietat al lloc (this._filtroProvider.value.texto = cambios.texto, cosa que mutaria l'objecte sense canviar la seva referència), crea un objecte completament nou mitjançant l'operador de propagació, combinant el valor anterior amb els canvis rebuts, i assigna aquest objecte nou a .value. Aquesta assignació canvia la referència del valor de context, exactament allò que ContextProvider necessita per notificar correctament els consumidors subscrits, el mateix mecanisme de detecció per referència explicat per a propietats reactives normals a l'apartat 3.
  3. La pregunta que ho hauria assenyalat és la primera: "Estic substituint aquesta propietat per un valor nou, o modificant el valor que ja tenia?", encara que aplicada aquí no a una mutació d'array, sinó a la relació entre el controlador i el seu host. Assignar directament this.host.cercaDeVencer = ... des de dins del controlador acobla el mecanisme intern del controlador a una propietat reactiva concreta que el host hauria de declarar només per donar cabuda a aquest detall d'implementació, exactament el mateix tipus d'acoblament innecessari que l'apartat 6 desaconsella entre responsabilitats que haurien de romandre separades: el controlador decideix el seu propi resultat; el host, a través de requestUpdate(), només s'assabenta de que ha de tornar a renderitzar-se, sense que totes dues responsabilitats es mesclin en una única propietat compartida.

Conclusió

Aquesta lliçó ha revisat, en cinc parells, els antipatrons més assenyalats al llarg del curs enfront de l'alternativa recomanada que TaskFlow ha aplicat en cada cas: immutabilitat en lloc de mutació al lloc, composed: true perquè els esdeveniments travessin el Shadow DOM, càlcul derivat fora de render(), components descompostos segons senyals concretes, i neteja simètrica de qualsevol recurs arrencat al cicle de vida. Amb aquest repàs, i amb les proves, l'accessibilitat i el rendiment de les tres lliçons anteriors, el mòdul 9 completa la cobertura de qualitat de TaskFlow.

Amb això es tanca també el contingut teòric del curs sencer. Queda una única lliçó, l'última del curs: "Projecte: Construint TaskFlow", on no es presenta cap concepte nou, sinó que es recorre l'aplicació de principi a fi, es repassen i completen les peces que al llarg del curs van quedar mencionades sense acabar del tot, i s'entrega TaskFlow com l'aplicació acabada que corona tot el recorregut, des del primer component Lit fins al projecte final.

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