Al llarg d'aquest mòdul has escrit diverses plantilles dins de render(), però en diversos exemples s'ha assenyalat explícitament que canviar un camp d'instància des de la consola del navegador no actualitza la pantalla. Abans d'entendre per què (i de resoldre-ho, al mòdul 3, amb propietats reactives de veritat), convé comprendre com funciona realment el cicle de renderitzat de Lit: quan decideix tornar a executar render(), per què aquest procés és asíncron, i quines regles calen respectar en escriure aquest mètode per no acabar amb comportaments estranys. Aquesta lliçó tanca el mòdul 2 amb aquesta base conceptual, i prepara el terreny per al mòdul 3, on <task-card> i <task-list> deixaran per fi de dependre de camps d'instància sense reactivitat.

Contingut

  1. render() no es crida sol: cal un disparador
  2. requestUpdate(): el disparador manual, a alt nivell
  3. Per què les actualitzacions són asíncrones
  4. Agrupació (batching) de diversos canvis en un únic renderitzat
  5. render() com a funció pura del seu estat
  6. Per què no s'ha de tocar el DOM a mà dins de render()
  7. Tancament del mòdul: cap a les propietats reactives de veritat

  1. render() no es crida sol: cal un disparador

Durant tot aquest mòdul, render() s'ha executat sempre en el mateix moment: quan el component s'insereix per primera vegada a la pàgina. Això explica per què, als exemples anteriors, canviar un camp d'instància des de la consola del navegador (elemento.titulo = 'Otro título') no produeix cap canvi visible: render() ja s'ha executat una vegada, en inserir el component, i res li ha indicat a Lit que ha de tornar a executar-se.

Això no és un defecte ni una limitació temporal del curs: és, senzillament, com funciona el mecanisme intern de Lit. render() no es torna a cridar automàticament perquè sí; necessita un disparador explícit que li indiqui a Lit "alguna cosa ha canviat, cal actualitzar". Aquest disparador, en l'ús normal de Lit, és un canvi en una propietat reactiva declarada amb static properties (o amb el decorador @property), tal com es va mencionar per damunt a la lliçó d'anatomia del mòdul 1. Quan s'assigna un valor nou a una propietat reactiva, Lit ho detecta internament (mitjançant un mecanisme de setters que instal·la automàticament sobre aquestes propietats concretes) i programa una actualització.

Els camps d'instància simples utilitzats en aquest mòdul (this.titulo, this.estado, this.tareas...) són camps de JavaScript corrents, sense cap setter especial instal·lat per Lit al damunt. Assignar-los un valor nou és una operació de JavaScript completament normal que Lit ni tan sols arriba a "veure". D'aquí que, sense propietats reactives, no existeixi cap disparador automàtic: el mòdul 3 resoldrà exactament aquesta peça que falta.

  1. requestUpdate(): el disparador manual, a alt nivell

Encara que el disparador habitual sigui el canvi d'una propietat reactiva, Lit exposa també un mètode que qualsevol component pot cridar directament per forçar una actualització sense dependre d'aquest mecanisme: this.requestUpdate().

import { LitElement, html } from 'lit';

class TaskCard extends LitElement {
  constructor() {
    super();
    this.titulo = 'Preparar la demo del sprint';
  }

  cambiarTitulo(nuevoTitulo) {
    this.titulo = nuevoTitulo;
    this.requestUpdate(); // li diem explícitament a Lit: "actualitza"
  }

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

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

En aquest exemple, cambiarTitulo modifica el camp this.titulo (un simple camp d'instància, igual que a la resta del mòdul) i, just després, crida this.requestUpdate() per demanar-li a Lit, de forma explícita, que torni a executar render(). Si des de la consola del navegador es cridés elemento.cambiarTitulo('Nuevo título'), aquesta vegada sí que es veuria el canvi reflectit a la pantalla, perquè hi ha hagut una crida explícita a requestUpdate().

És important situar correctament aquesta peça dins del curs: requestUpdate() és, essencialment, el mecanisme de baix nivell que Lit utilitza internament quan detecta un canvi en una propietat reactiva; conèixer-lo ara ajuda a entendre que la "màgia" de les propietats reactives del mòdul 3 no és cap màgia, sinó exactament aquesta mateixa crida, disparada automàticament per Lit en el moment adequat. L'estudi detallat de requestUpdate() —els seus paràmetres opcionals, en quins casos concrets convé cridar-lo manualment fins i tot amb propietats reactives ja declarades— pertany als mòduls 3 i 6; aquí n'hi ha prou amb reconèixer que existeix i per a què serveix a grans trets.

  1. Per què les actualitzacions són asíncrones

Una particularitat del cicle de renderitzat de Lit, que sorprèn a qui la veu per primera vegada, és que cridar requestUpdate() (o, al mòdul 3, canviar una propietat reactiva) no actualitza el DOM de forma immediata, a la mateixa línia de codi. En lloc d'això, Lit programa l'actualització perquè s'executi a la següent "microtasca" del bucle d'esdeveniments de JavaScript, un concepte propi de la plataforma web (no de Lit) que determina quan s'executa codi en cua un cop acaba la tasca síncrona actual.

cambiarTitulo(nuevoTitulo) {
  this.titulo = nuevoTitulo;
  this.requestUpdate();

  // En aquest punt, el DOM ENCARA no s'ha actualitzat.
  console.log(this.shadowRoot.querySelector('h3').textContent);
  // Mostra el títol ANTERIOR, no el nou.
}

Si cal esperar que l'actualització s'hagi aplicat realment al DOM abans de continuar executant codi, Lit ofereix una propietat especial, this.updateComplete, que és una Promise que es resol just quan l'actualització pendent acaba:

async cambiarTitulo(nuevoTitulo) {
  this.titulo = nuevoTitulo;
  this.requestUpdate();

  await this.updateComplete;

  // Ara sí, el DOM ja reflecteix el nou títol.
  console.log(this.shadowRoot.querySelector('h3').textContent);
}

No cal memoritzar updateComplete en detall en aquest punt del curs (es reprendrà quan resulti rellevant, més endavant); l'important és interioritzar la idea general: una actualització de Lit no passa en el mateix instant en què es produeix el canvi que l'origina, sinó una mica després, de forma asíncrona.

  1. Agrupació (batching) de diversos canvis en un únic renderitzat

El fet que les actualitzacions siguin asíncrones no és un simple detall tècnic curiós: és el que permet a Lit una optimització important anomenada batching (agrupació). Si, dins de la mateixa funció síncrona, es modifiquen diverses propietats reactives seguides, Lit no executa render() una vegada per cada canvi; espera fins al final d'aquesta funció i executa render() una sola vegada, amb tots els canvis ja aplicats a la vegada.

actualizarVariosCampos() {
  this.titulo = 'Nuevo título';
  this.estado = 'hecha';
  this.prioridad = 5;
  // Encara que s'han modificat tres propietats reactives,
  // Lit només executarà render() una vegada, no tres.
}

Aquesta agrupació és possible precisament gràcies al retard fins a la següent microtasca explicat a l'apartat anterior: com que l'actualització real no passa immediatament, Lit té una petita finestra de temps per "recollir" tots els canvis que es produeixin durant l'execució síncrona actual i aplicar-los junts en una sola passada de render(). Si les actualitzacions fossin síncrones i immediates, cadascuna de les tres assignacions de l'exemple anterior dispararia la seva pròpia execució completa de render(), malbaratant feina en dos renderitzats intermedis el resultat dels quals mai arriba a mostrar-se (perquè de seguida es sobreescriu amb el següent canvi).

Aquesta és una de les raons de fons, juntament amb l'anàlisi de plantilles ja en memòria cau explicada a la primera lliçó del mòdul, per les quals Lit es pot permetre un model de "canvia la propietat i llest" sense preocupar-se pel cost d'actualitzacions freqüents: el propi sistema agrupa automàticament la feina.

  1. render() com a funció pura del seu estat

Una idea que convé fixar amb claredat, perquè orienta com s'ha d'escriure qualsevol render(): aquest mètode ha de comportar-se com una funció pura de l'estat actual del component. És a dir, donat el mateix estat intern (els mateixos valors de les seves propietats i camps), render() ha de retornar sempre el mateix resultat, sense produir cap efecte secundari observable fora d'ell mateix.

A la pràctica, això significa evitar dins de render():

  • Modificar qualsevol propietat o camp del propi component (this.algo = ...), perquè això podria disparar una nova actualització des de dins de l'actualització actual, generant bucles difícils de depurar.
  • Realitzar peticions de xarxa, temporitzadors, o qualsevol operació amb efectes externs: render() pot arribar a executar-se diverses vegades pel propi funcionament intern de Lit, i no és el lloc adequat per llançar operacions que no s'haurien de repetir innecessàriament.
  • Dependre de dades externes que canviïn sense que Lit ho sàpiga, com llegir directament Math.random() o Date.now() i esperar un resultat estable; si aquests valors han d'aparèixer a la interfície, és millor calcular-los una vegada (per exemple, al constructor o en un callback del cicle de vida, segons correspongui) i desar-los com a part de l'estat del component.

Aquesta filosofia —render() com una traducció directa i predictible d'"estat actual" a "HTML actual", sense sorpreses ni efectes col·laterals— és compartida per pràcticament totes les biblioteques modernes d'interfícies, no només per Lit, i és la que fa possible raonar sobre un component simplement mirant les seves dades, sense haver de rastrejar una cadena d'efectes secundaris ocults.

  1. Per què no s'ha de tocar el DOM a mà dins de render()

Directament relacionada amb la idea de funció pura de l'apartat anterior hi ha una regla molt concreta: mai s'ha de manipular el DOM manualment dins de render(), ni amb document.querySelector, ni amb this.shadowRoot.querySelector, ni assignant directament innerHTML a algun node.

// Antipatró: NO facis això dins de render()
render() {
  const resultado = html`<article><h3>${this.titulo}</h3></article>`;

  // Malament: modificar el DOM manualment durant el propi renderitzat
  const posibleH3 = this.shadowRoot?.querySelector('h3');
  if (posibleH3) {
    posibleH3.classList.add('ya-renderizado');
  }

  return resultado;
}

El motiu és doble. Primer, com es va explicar a la primera lliçó del mòdul, és Lit qui decideix, a partir de l'anàlisi de la plantilla, com i quan actualitzar cada node concret del DOM; intervenir manualment competeix amb aquest mecanisme i pot fer que Lit perdi la pista de en quin estat real es troba un node, produint actualitzacions inconsistents. Segon, en el moment en què s'executa el cos de render(), el DOM resultant d'aquesta execució concreta potser encara no s'ha aplicat a la pàgina: render() només retorna una descripció del que s'hauria de mostrar, no l'aplica ell mateix, així que buscar nodes amb querySelector dins del propi render() pot trobar el DOM de la versió anterior, no de la que s'està generant.

Si un component necessita executar lògica que depengui del DOM ja actualitzat (per exemple, mesurar la mida real d'un element després de renderitzar-lo), el lloc correcte per fer-ho és algun dels callbacks del cicle de vida vistos al mòdul 1 —típicament updated() o firstUpdated()—, que Lit garanteix que s'executen després de que el DOM ja reflecteixi el resultat de render(). El funcionament detallat d'aquests callbacks és, novament, contingut del mòdul 6; per ara n'hi ha prou amb retenir la regla general: render() descriu, no manipula.

  1. Tancament del mòdul: cap a les propietats reactives de veritat

Amb aquesta lliçó es completa el recorregut del mòdul 2. Has aprés que html és una funció que separa l'estructura fixa d'una plantilla dels seus valors dinàmics, permetent actualitzacions eficients sense Virtual DOM; que dins de ${} es poden interpolar textos, atributs, propietats del DOM i valors booleans, a més d'expressions JavaScript arbitràries; que el renderitzat condicional i el renderitzat de llistes no necessiten sintaxi especial, només operadors i mètodes estàndard de JavaScript (?:, &&, Array.map); i, en aquesta última lliçó, que tot aquest procés de renderitzat es dispara mitjançant requestUpdate(), passa de forma asíncrona i agrupada, i ha de tractar render() com una funció pura sense efectes secundaris sobre el DOM.

Al llarg del mòdul, però, s'ha repetit una limitació de forma deliberada: <task-card> i <task-list> han utilitzat camps d'instància simples (this.titulo, this.tareas...) precisament per poder centrar l'atenció en com s'escriuen les plantilles, sense la complexitat afegida del sistema de reactivitat. Com s'ha vist a l'apartat 1 d'aquesta lliçó, aquests camps no disparen cap actualització automàtica perquè Lit no té manera de saber que han canviat: només vigila les propietats que s'han declarat explícitament com a reactives.

Aquesta és exactament la peça que falta, i que es resol al mòdul 3, "Propietats i Estat Reactiu". Allà aprendràs a declarar titulo, estado, prioridad i tareas com a propietats reactives de veritat mitjançant static properties, entenent com Lit instal·la automàticament el mecanisme de detecció de canvis sobre elles, com distingir entre propietats públiques i estat intern amb @state, i com es relacionen les propietats amb els atributs HTML. En aquest moment, tot el que has aprés en aquest mòdul 2 sobre plantilles, condicionals, llistes i cicle de renderitzat es combinarà amb la reactivitat real, i <task-card> i <task-list> començaran, per fi, a actualitzar-se soles quan canviïn les seves dades.

Errors Comuns i Consells

  • Esperar que el DOM estigui actualitzat just després de requestUpdate(): com s'explica a l'apartat 3, l'actualització és asíncrona; si cal comprovar el DOM després d'un canvi, cal esperar this.updateComplete (amb await) o comprovar-ho en un callback posterior del cicle de vida.
  • Cridar requestUpdate() de forma repetitiva i innecessària: si un component ja utilitza propietats reactives correctament declarades (contingut del mòdul 3), gairebé mai cal cridar requestUpdate() manualment; Lit ja ho fa automàticament en detectar el canvi. Cridar-lo "per si de cas" afegeix soroll i pot amagar errors reals en la declaració de les propietats.
  • Modificar propietats del propi component dins de render(): com s'ha explicat a l'apartat 5, això pot generar bucles d'actualització difícils de depurar, en els quals cada render() provoca sense voler un altre render() addicional.
  • Manipular el DOM intern amb querySelector dins de render(): com s'ha explicat a l'apartat 6, el lloc correcte per a aquest tipus de lògica són els callbacks posteriors a l'actualització (updated(), firstUpdated()), no el propi render().

Exercicis

  1. Retorna a l'exemple de l'apartat 2 (cambiarTitulo amb requestUpdate()) i amplia'l perquè, a més del títol, canviï també l'estat i la prioritat en la mateixa crida. Raona, basant-te en l'apartat 4, si això provocarà una o diverses execucions de render().
  2. Escriu, en pseudocodi o en JavaScript real si ho prefereixes, un mètode esperarYComprobar() que canviï this.titulo, cridi requestUpdate(), i després utilitzi await this.updateComplete per, a continuació, llegir amb this.shadowRoot.querySelector('h3').textContent el títol ja actualitzat en el DOM real.
  3. Identifica, en algun component que hagis escrit en lliçons anteriors d'aquest mòdul (task-card.js o task-list.js), si hi ha alguna línia dins de render() que infringeixi la regla de l'apartat 6 (manipular el DOM manualment). Si no n'hi ha, explica per què aquests components, tal com estan escrits, ja compleixen aquesta regla de forma natural.

Solucions

cambiarVariosCampos(nuevoTitulo, nuevoEstado, nuevaPrioridad) {
  this.titulo = nuevoTitulo;
  this.estado = nuevoEstado;
  this.prioridad = nuevaPrioridad;
  this.requestUpdate();
}

Encara que es cridi requestUpdate() una sola vegada de forma explícita en aquest exemple (i encara que es cridés tres vegades seguides, una per cada camp), Lit agrupa qualsevol actualització sol·licitada durant la mateixa execució síncrona i només executa render() una vegada, com s'ha explicat a l'apartat 4, mostrant ja els tres valors nous a la vegada.

async esperarYComprobar() {
  this.titulo = 'Título actualizado';
  this.requestUpdate();

  await this.updateComplete;

  const textoActual = this.shadowRoot.querySelector('h3').textContent;
  console.log(textoActual); // Ara sí, "Título actualizado"
}
  1. Als components task-card.js i task-list.js d'aquest mòdul, render() es limita sempre a construir i retornar una plantilla html a partir de camps d'instància (this.titulo, this.estado, this.tareas...), sense cridar en cap moment document.querySelector, this.shadowRoot.querySelector, ni assignacions directes d'innerHTML. Per tant, compleixen de forma natural la regla de l'apartat 6: descriuen el resultat desitjat mitjançant html, sense manipular el DOM pel seu compte, deixant aquesta responsabilitat exclusivament a Lit.

Conclusió

En aquesta última lliçó del mòdul 2 has entès el mecanisme que decideix quan i com Lit torna a executar render(): un disparador (habitualment el canvi d'una propietat reactiva, o requestUpdate() de forma manual), un procés asíncron que es resol a la següent microtasca i que agrupa diversos canvis en una sola actualització, i una regla de disseny fonamental: render() ha de comportar-se com una funció pura del seu estat, sense modificar propietats pròpies ni tocar el DOM manualment dins d'ell.

Amb això es tanca el mòdul "Plantilles Reactives i Renderitzat": ja saps escriure plantilles dinàmiques amb html, interpolar tot tipus de valors, renderitzar condicionalment i en forma de llistes, i entens el cicle intern que aplica aquests canvis al DOM. L'única cosa que ha faltat deliberadament durant tot el mòdul és el disparador automàtic real: unes propietats que, en canviar, activin aquesta maquinària per si soles. Això és, exactament, el punt de partida del mòdul 3, "Propietats i Estat Reactiu", on convertiràs els camps simples de <task-card> i <task-list> en propietats reactives de veritat amb static properties, i on cada targeta de TaskFlow podrà per fi mostrar les dades de la seva pròpia tasca de forma dinàmica i actualitzar-se sola quan aquestes dades canviïn.

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