En el mòdul anterior vas escriure diverses plantilles amb la funció html, però sempre amb contingut completament fix: el mateix títol, el mateix estat, la mateixa persona assignada a cada renderitzat. Ha arribat el moment d'entendre què és realment aquesta funció html i, sobretot, què passa per dins quan el seu contingut deixa de ser estàtic. Aquesta lliçó obre el mòdul 2 explicant el mecanisme intern del motor de plantilles de Lit: com s'analitza una plantilla, com s'actualitza el DOM quan canvien les dades, i per què aquest enfocament és molt més eficient que les alternatives clàssiques de manipular el DOM a mà o reconstruir-lo amb innerHTML.
Contingut
htmlno és una plantilla de text: és una funció- Tagged template literals: la base de
html - Com sap Lit quines parts són dinàmiques
- Actualitzar el DOM sense un Virtual DOM
- Per què això és diferent (i millor) que
innerHTML - Primera interpolació a
<task-card> - Important: això encara no és reactivitat completa
html no és una plantilla de text: és una funció
html no és una plantilla de text: és una funcióFins ara, cada vegada que has escrit alguna cosa com això:
render() {
return html`
<article>
<h3>Preparar la demo del sprint</h3>
<p>Estado: En curso</p>
</article>
`;
}és fàcil pensar en html\...`com si fos simplement una cadena de text amb HTML a dins, alguna cosa semblant a escriure". Aquesta intuïció és raonable a primera vista, però és incorrecta i amaga precisament el mecanisme que fa que Lit sigui ràpid i pràctic d'utilitzar. html` és, en realitat, una funció de JavaScript, i el que se li passa entre els accents greus no és una cadena normal, sinó una cosa anomenada tagged template literal ("plantilla de literal etiquetada"), una característica que forma part del mateix estàndard de JavaScript des de fa anys, sense cap dependència de Lit.
Quan el motor de JavaScript troba html\
Hola
`, no concatena text i crida htmlamb el resultat. En lloc d'això, invoca la funcióhtmlamb informació estructurada sobre la plantilla: d'una banda, un array amb els trossos de text literal ("estàtics"), i de l'altra, els valors que s'han interpolat dins de${}` (si n'hi ha). Aquesta distinció —trossos fixos d'una banda, valors dinàmics de l'altra— és la peça central de tot el que fa especial el motor de plantilles de Lit.
- Tagged template literals: la base de
html
htmlPer entendre bé què fa html, és útil veure primer com funciona un tagged template literal amb una funció d'exemple, sense Lit pel mig:
function miEtiqueta(trozos, ...valores) {
console.log(trozos); // ["Hola ", ", tienes ", " tareas"]
console.log(valores); // ["Ana", 3]
}
const nombre = 'Ana';
const numeroDeTareas = 3;
miEtiqueta`Hola ${nombre}, tienes ${numeroDeTareas} tareas`;En executar aquest codi, trozos rep un array amb els tres fragments de text que queden entre les interpolacions ("Hola ", ", tienes " i " tareas"), mentre que valores rep, en un array a part, els dos valors interpolats ("Ana" i 3). Aquesta separació és exactament el que fa possible que una funció etiquetada "sàpiga" quines parts de la plantilla són literals (mai canvien) i quines són expressions (poden canviar).
La funció html de Lit és, en essència, una funció etiquetada d'aquest mateix tipus, especialitzada a tractar el primer array com a HTML i el segon com a dades que cal inserir en punts concrets d'aquest HTML. La diferència respecte a l'exemple de miEtiqueta és que html no es limita a mostrar aquests valors per consola: construeix amb ells un objecte intern, anomenat TemplateResult, que descriu la plantilla de manera que Lit la pugui processar de manera eficient.
- Com sap Lit quines parts són dinàmiques
Aquí hi ha la clau de tot el mecanisme. Com que els trossos de text literal sempre arriben separats dels valors interpolats, Lit pot, la primera vegada que veu una plantilla concreta, analitzar únicament el seu "esquelet" fix (els trossos de text) i esbrinar exactament en quines posicions de l'HTML apareixeran valors dinàmics. Aquesta anàlisi es fa una única vegada per plantilla, no a cada renderitzat.
El procés, simplificat, és el següent:
- La primera vegada que Lit troba una determinada plantilla
html\...`(identificada internament pels seus trossos de text fixos), construeix un element` real d'HTML amb marcadors especials als buits on hi havia interpolacions. - Lit recorre aquest
<template>i anota amb precisió on estan aquests buits: poden ser el contingut d'un node de text, el valor d'un atribut, o directament un node sencer. - Aquesta anàlisi es desa en memòria cau, associada a aquesta plantilla concreta. Si el mateix component torna a renderitzar amb la mateixa estructura de plantilla (canviant només els valors interpolats), Lit no repeteix l'anàlisi: reutilitza directament el mapa de buits ja calculat.
- A cada renderitzat posterior, Lit només necessita clonar el
<template>(una operació molt econòmica al navegador) i col·locar els nous valors exactament als buits ja identificats, sense tornar a interpretar l'HTML complet.
Aquesta idea —separar d'una vegada l'"esquelet" fix dels "buits" variables— és la raó per la qual Lit es pot permetre actualitzacions extremadament ràpides: en un renderitzat normal, no es torna a analitzar (parse) cap etiqueta HTML; només es comparen els valors nous amb els anteriors a cada buit i, si han canviat, s'actualitza exactament aquest punt del DOM real.
- Actualitzar el DOM sense un Virtual DOM
Si has utilitzat altres biblioteques d'interfícies (React, Vue...) és possible que coneguis el concepte de "Virtual DOM": una representació en memòria, com un arbre d'objectes JavaScript, de com hauria de veure's la interfície. A cada actualització, aquestes biblioteques generen un nou arbre virtual complet i el comparen (mitjançant un algorisme de "diffing") amb l'arbre virtual anterior, per calcular quins canvis mínims cal aplicar al DOM real.
Lit segueix un enfocament diferent, més directe, precisament gràcies a l'anàlisi descrita a l'apartat anterior:
| Aspecte | Enfocament amb Virtual DOM | Enfocament de Lit |
|---|---|---|
| Què es genera a cada renderitzat | Un arbre complet d'objectes que representa tota la interfície | Únicament els valors nous a inserir als buits ja identificats |
| Com es detecten els canvis | Comparant (diffing) l'arbre nou contra l'arbre anterior, node a node |
Comparant directament el valor nou de cada buit contra el valor anterior d'aquest mateix buit |
| Cost d'un renderitzat sense canvis reals | Es genera igualment l'arbre nou complet, encara que el resultat del diffing sigui "no tocar res" | S'avaluen els buits, no es reconstrueix cap estructura, i no es toca el DOM si els valors no han canviat |
| On viu el coneixement de l'estructura | A l'arbre virtual, recalculat a cada render | A la plantilla <template> ja analitzada i emmagatzemada en memòria cau des del primer renderitzat |
En altres paraules: Lit no necessita "endevinar" què ha canviat comparant dos arbres complets, perquè ja sap, des de la primera anàlisi de la plantilla, exactament on estan els punts que poden canviar. Només ha de revisar aquests punts concrets. Això no significa que un enfocament sigui "dolent" i l'altre "bo" en termes absoluts —són estratègies diferents amb els seus propis avantatges—, però sí que explica per què Lit pot ser una biblioteca tan lleugera: no necessita implementar ni transportar en el seu codi un algorisme de diffing d'arbres complet.
- Per què això és diferent (i millor) que
innerHTML
innerHTMLUna altra alternativa habitual, especialment en codi sense cap biblioteca, és reconstruir l'HTML com una cadena de text i assignar-la mitjançant innerHTML:
// Enfoque manual con innerHTML, SIN Lit (solo con fines comparativos)
function actualizarTarjeta(contenedor, titulo, estado) {
contenedor.innerHTML = `
<article>
<h3>${titulo}</h3>
<p>Estado: ${estado}</p>
</article>
`;
}Aquest codi "funciona", en el sentit que mostra l'HTML correcte, però té problemes seriosos que el motor de plantilles de Lit evita per disseny:
- Destrueix i recrea tot l'arbre DOM a cada actualització: en assignar
innerHTML, el navegador descarta els nodes existents dins del contenidor i crea nodes completament nous a partir del text. Això és costós en termes de rendiment i, a més, fa que es perdi qualsevol estat intern d'aquests nodes (per exemple, el focus d'un camp de formulari, o el text que un usuari estigués seleccionant). - Torna a analitzar (parse) l'HTML sencer a cada crida: el navegador ha d'interpretar de nou tota la cadena de text com a HTML cada vegada, sense cap tipus de memòria cau ni reutilització d'anàlisis prèvies.
- Obre la porta a vulnerabilitats d'injecció d'HTML: si
titulooestadocontingueren contingut no confiable (per exemple, text escrit lliurement per un usuari amb etiquetes<script>a dins),innerHTMLho interpretaria com a HTML real, executant-ho potencialment. Lit, en canvi, insereix els valors interpolats com a text segur per defecte, sense interpretar-los com a HTML (excepte que s'utilitzi explícitament un mecanisme pensat per a aquest cas, que es veurà més endavant al curs).
Lit, gràcies a la memòria cau de plantilles de l'apartat 3, actualitza únicament els nodes de text o atributs concrets que han canviat, conservant intacte la resta de l'arbre DOM. Aquesta és una diferència fonamental de comportament, no només de rendiment.
- Primera interpolació a
<task-card>
<task-card>Amb aquesta base ja es pot fer el primer pas pràctic del mòdul: interpolar un valor dins de <task-card>. Recupera el component de la lliçó "Tu Primer Componente Lit" i modifica'l així:
import { LitElement, html } from 'lit';
class TaskCard extends LitElement {
constructor() {
super();
// Un camp d'instància simple, encara sense el sistema
// de propietats reactives de Lit (això arriba al mòdul 3).
this.titulo = 'Preparar la demo del sprint';
}
render() {
return html`
<article>
<h3>${this.titulo}</h3>
<p>Estado: En curso</p>
<p>Asignada a: Ana</p>
</article>
`;
}
}
customElements.define('task-card', TaskCard);El canvi respecte a la versió anterior és petit però important: al constructor es defineix this.titulo com un camp d'instància normal de JavaScript, i a render() s'interpola ${this.titulo} dins de l'<h3>, en lloc del text fix "Preparar la demo del sprint". En recarregar la pàgina, el resultat visual és idèntic al d'abans: es continua mostrant "Preparar la demo del sprint", perquè aquest és el valor que s'ha assignat a this.titulo. La diferència no és en el que es veu, sinó en com es genera: ara el títol prové d'una expressió JavaScript (${this.titulo}), no de text gravat directament a la plantilla.
Pots comprovar-ho modificant el valor a mà, per exemple a la consola del navegador, obtenint primer una referència a l'element:
// Desde la consola del navegador (con <task-card> ya en la página):
const tarjeta = document.querySelector('task-card');
tarjeta.titulo = 'Revisar el backlog';Si executes això i no passa res visible a la pantalla, no et preocupis: és exactament el comportament esperat en aquest punt del curs, i s'explica al següent apartat.
- Important: això encara no és reactivitat completa
És fonamental deixar clar un matís abans de seguir avançant en el mòdul, per no generar expectatives equivocades: this.titulo a l'exemple anterior és, de moment, un simple camp d'instància de JavaScript, sense cap relació encara amb el sistema de propietats reactives que ofereix Lit. Si vas executar el codi de la consola de l'apartat anterior (tarjeta.titulo = 'Revisar el backlog'), hauràs comprovat que la targeta a pantalla no canvia, encara que el valor de this.titulo sí que s'hagi actualitzat internament.
Això passa perquè, tal com s'ha explicat a l'apartat 3, Lit només torna a executar render() i actualitzar el DOM quan detecta que una propietat reactiva ha canviat, una cosa que encara no s'ha declarat en aquest component. Declarar titulo com una propietat reactiva de veritat (amb static properties, com es va apuntar a la lliçó d'anatomia del mòdul 1) és precisament el contingut central del mòdul 3, "Propietats i Estat Reactiu".
Durant la resta d'aquest mòdul 2, seguiràs utilitzant camps d'instància simples com this.titulo per poder centrar-te, sense distraccions, en com s'escriuen les plantilles: com interpolar diversos tipus de valors, com renderitzar condicionalment, com renderitzar llistes i com funciona el cicle de renderitzat. El mateix render() s'executarà explícitament quan ho necessitis per veure els canvis (per exemple, cridant de forma manual a un mètode que veuràs a l'última lliçó del mòdul), però l'actualització automàtica en canviar un valor —la veritable reactivitat— és terreny del mòdul 3. Tingues-ho present: si alguna cosa no s'actualitza "sola" en els pròxims exemples, és intencionat i no un error.
Errors Comuns i Consells
- Pensar que
htmlretorna una cadena de text: és un error molt estès en començar amb Lit.html\...`retorna un objecteTemplateResult, no una cadena. Si intentes concatenar-lo amb+` o utilitzar-lo on s'espera text, no obtindràs el resultat esperat. - Esperar que canviar un camp d'instància normal actualitzi la pantalla: com s'ha explicat a l'apartat 7, això és exactament el que passa en aquest punt del curs, expressament. L'actualització automàtica arriba amb les propietats reactives del mòdul 3.
- Confondre la memòria cau de plantilles amb memòria cau de dades: el que Lit desa en memòria cau és l'anàlisi de l'estructura de la plantilla (on estan els buits), no els valors concrets que s'han interpolat en un moment donat. Cada renderitzat continua utilitzant els valors actuals.
- Utilitzar
innerHTML"a mà" dins d'un component Lit per barrejar enfocaments: si necessites mostrar HTML dinàmic, fes-ho sempre a través de la funcióhtmli les seves interpolacions, no barrejant manipulació directa del DOM amb el sistema de plantilles de Lit; barrejar tots dos enfocaments pot confondre Lit sobre quins nodes gestiona ell i quins no.
Exercicis
- Explica amb les teves paraules (dues o tres frases) què són els dos arrays que rep una funció etiquetada com
html, i per què aquesta separació és important per al rendiment de Lit. - Afegeix a
<task-card>un segon camp d'instància,this.estado, inicialitzat a'En curso'al constructor, i interpola'l a la plantilla substituint el text fix "En curso" del paràgraf d'estat. - Des de la consola del navegador, canvia
tarjeta.estadoa un altre valor i comprova que, efectivament, la pantalla no s'actualitza. Anota en un comentari per què passa això, relacionant-ho amb el que s'ha explicat a l'apartat 7.
Solucions
-
Una funció etiquetada com
htmlrep, d'una banda, un array amb els fragments de text literal de la plantilla (les parts que mai canvien) i, de l'altra, un array amb els valors que s'han interpolat amb${}. Aquesta separació és important perquè permet a Lit analitzar una sola vegada l'"esquelet" fix de la plantilla (on estan els buits) i, en renderitzats posteriors, reutilitzar aquesta anàlisi sense tornar a analitzar l'HTML, inserint directament els nous valors als buits ja coneguts.
import { LitElement, html } from 'lit';
class TaskCard extends LitElement {
constructor() {
super();
this.titulo = 'Preparar la demo del sprint';
this.estado = 'En curso';
}
render() {
return html`
<article>
<h3>${this.titulo}</h3>
<p>Estado: ${this.estado}</p>
<p>Asignada a: Ana</p>
</article>
`;
}
}
customElements.define('task-card', TaskCard);- En executar
tarjeta.estado = 'Bloqueada'des de la consola, la propietat interna de l'objecte canvia (es pot comprovar ambconsole.log(tarjeta.estado), que mostrarà'Bloqueada'), però la pantalla continua mostrant "Estado: En curso". Això passa perquèestado, igual quetitulo, és en aquest moment un camp d'instància normal de JavaScript, no una propietat reactiva registrada davant Lit; Lit només torna a executarrender()quan detecta canvis en propietats de les quals ha estat informat que ha de vigilar, una cosa que es configurarà ambstatic propertiesal mòdul 3.
Conclusió
En aquesta lliçó has obert el mòdul 2 desmuntant el mecanisme intern de la funció html: és una funció etiquetada estàndard de JavaScript que separa els trossos fixos d'una plantilla dels seus valors dinàmics, el que permet a Lit analitzar l'estructura una sola vegada i, a cada renderitzat posterior, actualitzar únicament els buits concrets del DOM real, sense necessitat d'un Virtual DOM ni de reconstruir l'HTML amb innerHTML. També has fet el primer pas pràctic interpolant camps d'instància simples a <task-card>, deixant clar que això encara no activa cap actualització automàtica: aquesta peça arriba amb les propietats reactives del mòdul 3.
A la següent lliçó, "Expresiones e Interpolación en Plantillas", aprofundiràs en els diferents tipus de valors que es poden interpolar dins d'una plantilla html —text, atributs, propietats del DOM amb .prop, valors booleans amb ?attr— i ampliaràs <task-card> per mostrar diversos camps alhora, incloent-hi una primera pinzellada d'expressions JavaScript més elaborades dins de ${}.
Curs de Lit
Mòdul 1: Introducció a Lit i Web Components
- Què són els Web Components i per què Lit?
- Configuració de l'Entorn de Desenvolupament
- El Teu Primer Component Lit
- Anatomia d'un Component Lit
Mòdul 2: Plantilles Reactives i Renderitzat
- El Motor de Plantilles de Lit
- Expressions i Interpolació en Plantilles
- Renderitzat Condicional
- Renderitzat de Llistes
- El Cicle de Renderitzat
Mòdul 3: Propietats i Estat Reactiu
- Propietats Reactives
- Estat Intern amb @state
- Tipus de Propietats i Conversors Personalitzats
- Atributs vs Propietats i Reflexió
Mòdul 4: Estils en Components Lit
- CSS Encapsulat amb Shadow DOM
- Estils Compartits entre Components
- Variables CSS Personalitzades i Theming
- Slots i Estilitzat de Contingut Distribuït
Mòdul 5: Esdeveniments i Comunicació entre Components
- Gestió d'Esdeveniments DOM en Plantilles
- Esdeveniments Personalitzats: Comunicació de Fill a Pare
- Comunicació de Pare a Fill amb Propietats
- Patrons de Comunicació entre Components Germans
Mòdul 6: Cicle de Vida i Comportament Avançat
- Callbacks del Cicle de Vida
- Hooks Reactius: willUpdate, updated i firstUpdated
- Controladors Reactius
- Mixins i Composició de Comportament
Mòdul 7: Directives i Funcionalitats Avançades de Plantilles
- Directives Incorporades: classMap, styleMap i ifDefined
- Directives Personalitzades
- Renderitzat Asíncron amb until
- Context Compartit amb @lit/context
Mòdul 8: Integració, Interoperabilitat i Desplegament
- Utilitzar Components Lit en HTML Pla
- Integrar Lit amb React, Vue i Angular
- Renderitzat al Servidor amb @lit-labs/ssr
- Empaquetatge, Publicació i TypeScript
Mòdul 9: Proves i Bones Pràctiques
- Proves Unitàries amb Web Test Runner
- Accessibilitat en Web Components
- Rendiment i Optimització
- Patrons i Antipatrons Comuns
