Les propietats de <task-card> declarades fins ara utilitzen tipus senzills: String, Number i Boolean. Però un component real sovint necessita gestionar tipus de dada més rics: col·leccions, objectes estructurats, o fins i tot tipus que no existeixen de manera nativa en HTML, com una data. Aquesta lliçó repassa el catàleg complet de tipus que Lit sap convertir de sèrie entre atribut i propietat, s'atura en un problema clàssic amb Boolean, i ensenya a escriure un conversor personalitzat per a un tipus propi, que s'aplicarà a una nova propietat fechaLimite de <task-card>.
Contingut
- El problema de fons: HTML només entén text
- Els tipus suportats de sèrie:
String,Number,Boolean,Array,Object - El problema clàssic de
Booleanamb atributs - Quan cal un conversor personalitzat
- Escrivint un
convertera mida - Aplicant el conversor a
fechaLimitea<task-card>
- El problema de fons: HTML només entén text
Abans d'entrar en el catàleg de tipus, convé fixar la raó per la qual existeix tot aquest mecanisme de conversió. Un atribut HTML, per definició del propi estàndard de la plataforma web, sempre és una cadena de text. Quan s'escriu <task-card prioridad="5">, el navegador no emmagatzema internament el número 5; emmagatzema literalment el text "5". Això és així per a qualsevol atribut de qualsevol element HTML, no només per als Custom Elements: <input type="number" value="5"> també desa "5" com a text a l'atribut value, encara que el camp es comporti visualment com a numèric.
Ara bé, en JavaScript interessa treballar amb els tipus de dada reals: un número de veritat per poder fer operacions aritmètiques amb prioridad, un booleà de veritat per poder utilitzar urgente directament en una condició, i així successivament. La responsabilitat de Lit, quan es declara type a static properties, és exactament fer de pont entre els dos mons: convertir el text de l'atribut al tipus JavaScript declarat quan l'atribut canvia, i convertir el valor JavaScript de nou a text quan calgui reflectir-lo a l'atribut (una cosa que es detalla a la lliçó següent, sobre reflexió).
- Els tipus suportats de sèrie:
String, Number, Boolean, Array, Object
String, Number, Boolean, Array, ObjectLit reconeix, de fàbrica, cinc valors per a l'opció type, cada un amb la seva pròpia lògica de conversió des del text de l'atribut:
type |
Com converteix d'atribut (text) a propietat | Exemple d'atribut | Valor resultant en JS |
|---|---|---|---|
String |
S'utilitza el text tal com és, sense transformar | titulo="Revisar el PR" |
"Revisar el PR" |
Number |
S'aplica Number(valorDelAtributo) |
prioridad="5" |
5 (número) |
Boolean |
Presència/absència de l'atribut, no el seu contingut textual (es detalla a l'apartat 3) | urgente |
true |
Array |
S'interpreta el text de l'atribut com a JSON i es parseja amb JSON.parse |
etiquetas='["urgente","cliente"]' |
['urgente', 'cliente'] |
Object |
Igual que Array, mitjançant JSON.parse sobre el text de l'atribut |
metadatos='{"autor":"Ana"}' |
{ autor: 'Ana' } |
Els tres primers tipus (String, Number, Boolean) són els que s'han utilitzat a <task-card> fins ara, i el seu comportament és raonablement intuïtiu excepte pel cas de Boolean, que s'explica a l'apartat següent. Els dos últims (Array, Object) són menys habituals d'establir directament com a text en un atribut HTML —escriure JSON vàlid a mà dins d'una etiqueta HTML és incòmode i propens a errors d'escapament de cometes—, però resulten perfectament naturals quan la propietat s'assigna des de JavaScript, sense passar en cap moment per la seva representació en text:
// Assignació directa des de JavaScript: no cal cap JSON.parse manual, // perquè no es passa per la representació en text de l'atribut. tarjeta.etiquetas = ['urgente', 'cliente'];
De fet, aquest és el patró més habitual en aplicacions reals construïdes amb Lit: els tipus compostos (Array, Object) gairebé sempre s'assignen des de JavaScript, mentre que els atributs HTML en el propi marcatge es reserven sobretot per a tipus simples (String, Number, Boolean), que són els que té sentit escriure a mà en una plantilla HTML.
- El problema clàssic de
Boolean amb atributs
Boolean amb atributsEl tipus Boolean mereix una explicació a part perquè el seu comportament sorprèn qui el veu per primera vegada, i és una font freqüent d'errors. La conversió de Boolean no mira el contingut textual de l'atribut, sinó únicament si l'atribut hi és present o absent a l'etiqueta, seguint la mateixa convenció que utilitzen els atributs booleans natius d'HTML (disabled, checked, required...).
<!-- urgente vale true: el atributo está presente, con cualquier contenido o sin ninguno --> <task-card urgente></task-card> <task-card urgente="true"></task-card> <task-card urgente="false"></task-card> <task-card urgente=""></task-card> <!-- urgente vale false: el atributo, sencillamente, no está --> <task-card></task-card>
Aquest és, de llarg, l'error més habitual en treballar amb propietats Boolean a Lit: escriure urgente="false" esperant que la propietat valgui false, quan en realitat l'atribut hi és present (té el text "false", però hi és), així que Lit l'interpreta com a true. L'única manera que una propietat Boolean valgui false a través d'un atribut HTML és que aquest atribut no aparegui en absolut a l'etiqueta.
Aquesta convenció no és una raresa inventada per Lit: és exactament com funcionen els atributs booleans natius del navegador. <input disabled="false"> continua deshabilitant el camp, perquè l'atribut disabled hi és present; per habilitar-lo, cal treure l'atribut per complet, no posar-lo a "false". Lit simplement respecta aquesta mateixa convenció per mantenir la coherència amb la resta de la plataforma web.
Des de JavaScript, en canvi, no existeix aquesta ambigüitat: assignar tarjeta.urgente = false funciona exactament com s'espera, perquè aquí no hi ha cap text pel mig, només el valor booleà real de JavaScript.
- Quan cal un conversor personalitzat
Els cinc tipus de l'apartat 2 cobreixen la immensa majoria dels casos habituals, però existeixen tipus de dada que no encaixen de manera natural en cap d'ells. L'exemple que s'utilitzarà en aquesta lliçó és una data: si <task-card> necessita una propietat fechaLimite, quin tipus hauria de declarar?
- Declarar-la com a
Stringfuncionaria a mitges: l'atribut funcionaria, però la propietat JavaScript seria simplement una cadena de text, sense cap de les capacitats d'un objecteDate(comparar dates, calcular quants dies falten, formatar de maneres diferents). - Declarar-la com a
Objecttampoc encaixa bé: un objecteDatede JavaScript no es serialitza de manera útil ambJSON.stringify(produeix una cadena en format ISO dins de cometes, però en ferJSON.parsede tornada no es reconstrueix automàticament com un objecteDate, sinó com una cadena de text normal).
Per a aquest tipus de situacions, Lit permet substituir la conversió automàtica basada en type per una funció de conversió completament a mida, mitjançant l'opció converter.
- Escrivint un
converter a mida
converter a midaUn converter és un objecte amb fins a dues funcions: fromAttribute, que converteix el text de l'atribut al valor de la propietat, i toAttribute, que fa el camí invers (necessària només si a més s'utilitza reflect: true, que s'explica a la lliçó següent). Per al cas de fechaLimite, interessa convertir entre una cadena de text en format AAAA-MM-DD (el format habitual d'un atribut de data en HTML, el mateix que utilitza <input type="date">) i un objecte Date real de JavaScript:
const conversorDeFecha = {
fromAttribute(valorDelAtributo) {
// valorDelAtributo és el text de l'atribut, o null si no hi és present
if (!valorDelAtributo) {
return null;
}
return new Date(valorDelAtributo);
},
toAttribute(valorDeLaPropiedad) {
// valorDeLaPropiedad és l'objecte Date (o null) de la propietat
if (!valorDeLaPropiedad) {
return null;
}
return valorDeLaPropiedad.toISOString().split('T')[0]; // "AAAA-MM-DD"
},
};Analitzem cada funció per separat. fromAttribute rep el text de l'atribut tal com és a l'HTML (per exemple, "2026-07-15") i ha de retornar el valor que tindrà la propietat JavaScript: aquí, un objecte Date construït amb new Date(valorDelAtributo), que interpreta correctament una cadena en format AAAA-MM-DD. toAttribute fa el camí contrari: rep l'objecte Date de la propietat i ha de retornar el text que s'escriurà a l'atribut si la propietat es reflecteix; aquí s'utilitza toISOString() (que retorna una data completa amb hora, en format 2026-07-15T00:00:00.000Z) i es retalla amb split('T')[0] per quedar-se només amb la part de la data.
Ambdues funcions comproven primer si el valor d'entrada és "falsy" (null, undefined, cadena buida) i retornen null en aquest cas, per evitar errors si la propietat encara no té data assignada; és una precaució raonable en qualsevol conversor personalitzat, ja que Lit pot cridar aquestes funcions en moments en què el valor encara no s'ha establert.
- Aplicant el conversor a
fechaLimite a <task-card>
fechaLimite a <task-card>Amb el conversor ja escrit, s'aplica a la propietat indicant-lo a l'objecte de configuració de static properties, en lloc de (o juntament amb) type:
import { LitElement, html } from 'lit';
const conversorDeFecha = {
fromAttribute(valorDelAtributo) {
if (!valorDelAtributo) {
return null;
}
return new Date(valorDelAtributo);
},
toAttribute(valorDeLaPropiedad) {
if (!valorDeLaPropiedad) {
return null;
}
return valorDeLaPropiedad.toISOString().split('T')[0];
},
};
class TaskCard extends LitElement {
static properties = {
titulo: { type: String },
estado: { type: String },
prioridad: { type: Number },
urgente: { type: Boolean },
expandida: { state: true },
fechaLimite: { converter: conversorDeFecha, attribute: 'fecha-limite' },
};
constructor() {
super();
this.titulo = 'Tarea sin título';
this.estado = 'pendiente';
this.prioridad = 3;
this.urgente = false;
this.expandida = false;
this.fechaLimite = null;
}
renderInsigniaEstado() {
if (this.estado === 'hecha') {
return html`<span class="insignia insignia--hecha">✓ Hecha</span>`;
}
if (this.estado === 'en-progreso') {
return html`<span class="insignia insignia--progreso">◐ En progreso</span>`;
}
return html`<span class="insignia insignia--pendiente">○ Pendiente</span>`;
}
renderFechaLimite() {
if (!this.fechaLimite) {
return '';
}
return html`<p>Fecha límite: ${this.fechaLimite.toLocaleDateString('es-ES')}</p>`;
}
render() {
return html`
<article>
<h3>${this.titulo}</h3>
${this.renderInsigniaEstado()}
<p>Prioridad: ${this.prioridad}</p>
${this.renderFechaLimite()}
${this.urgente && html`<p class="aviso">⚠ Urgente</p>`}
</article>
`;
}
}
customElements.define('task-card', TaskCard);Amb aquesta declaració, <task-card fecha-limite="2026-07-15"> fa que this.fechaLimite sigui, dins del component, un objecte Date real, no una cadena de text: es pot cridar this.fechaLimite.toLocaleDateString('es-ES') (com es fa a renderFechaLimite) o qualsevol altre mètode de Date, sense necessitat de convertir res manualment a render(). Això és exactament el valor d'un conversor personalitzat: trasllada la responsabilitat de la conversió a l'únic lloc on es declara la propietat, en lloc de repetir-la cada vegada que es llegeix el valor.
Nota també que s'ha indicat explícitament attribute: 'fecha-limite', aplicant el que es va aprendre a la primera lliçó del mòdul sobre noms d'atribut en kebab-case per a propietats amb nom compost en camelCase.
Errors Comuns i Consells
- Escriure
urgente="false"esperant que la propietat valguifalse: com es va explicar a l'apartat 3, ambtype: Booleanel que importa és la presència o absència de l'atribut, no el seu contingut de text; perquè valguifalsedes d'HTML, l'atribut s'ha d'ometre per complet. - Intentar declarar
type: Dateesperant que Lit ho suporti de sèrie:Dateno és un dels cinc tipus reconeguts automàticament (apartat 2); si es declara{ type: Date }, Lit el tractarà de manera equivalent aStringsense cap tipus de conversió especial, ithis.fechaLimiteseria simplement el text de l'atribut, no un objecteDate. Per a tipus no suportats de sèrie cal unconverterpersonalitzat, com s'ha vist a l'apartat 5. - Oblidar comprovar valors nuls o buits dins de
fromAttribute/toAttribute: si el conversor de l'apartat 5 no comprovésif (!valorDelAtributo)al principi, cridarnew Date(null)onew Date(undefined)produiria una data no vàlida (Invalid Date) en lloc de fallar de manera clara o retornarnull, la qual cosa pot provocar errors confusos més endavant a la plantilla. - Passar JSON mal formatat a un atribut de tipus
ArrayoObject: escriure a màetiquetas='["urgente", "cliente"]'dins d'un atribut HTML és propens a errors de cometes (les cometes dobles del JSON xoquen amb les cometes del propi atribut si no es gestionen amb cura); a la pràctica, per a tipus compostos sol ser més fiable assignar la propietat des de JavaScript, com s'ha apuntat a l'apartat 2.
Exercicis
- Sense utilitzar cap conversor personalitzat, declara a
<task-card>una propietatetiquetasde tipusArray, amb valor inicial[], i mostra-la a la plantilla com una llista separada per comes (this.etiquetas.join(', ')). Comprova, assignant-la des de la consola del navegador ambelemento.etiquetas = ['cliente', 'urgente'], que la plantilla s'actualitza. - Recupera el conversor
conversorDeFechade l'apartat 5 i afegeix una comprovació addicional afromAttributeque, si elnew Date(...)resultant fos no vàlid (es pot comprovar ambNumber.isNaN(fecha.getTime())), retorninullen lloc d'una data no vàlida. - Explica amb les teves pròpies paraules per què
<task-card urgente="false"></task-card>no deshabilita l'avís d'urgència, i què caldria escriure en lloc d'això perquèurgentevalguésfalse.
Solucions
static properties = {
// ...propietats anteriors...
etiquetas: { type: Array },
};
constructor() {
super();
// ...
this.etiquetas = [];
}
render() {
return html`
<article>
<h3>${this.titulo}</h3>
<p>Etiquetas: ${this.etiquetas.join(', ')}</p>
</article>
`;
}En executar elemento.etiquetas = ['cliente', 'urgente'] des de la consola, es reassigna per complet la propietat etiquetas (no es muta l'array existent), així que el setter reactiu es dispara amb normalitat i la plantilla mostra "Etiquetas: cliente, urgente".
const conversorDeFecha = {
fromAttribute(valorDelAtributo) {
if (!valorDelAtributo) {
return null;
}
const fecha = new Date(valorDelAtributo);
if (Number.isNaN(fecha.getTime())) {
return null;
}
return fecha;
},
toAttribute(valorDeLaPropiedad) {
if (!valorDeLaPropiedad) {
return null;
}
return valorDeLaPropiedad.toISOString().split('T')[0];
},
};- Amb
type: Boolean, Lit determina el valor de la propietat únicament per si l'atribut hi és present a l'etiqueta, sense fixar-se en el seu contingut de text; com queurgente="false"continua tenint l'atributurgentepresent (amb el text"false"a dins, però present al cap i a la fi), Lit l'interpreta com atrue, igual que passa amb atributs natius comdisabled. Perquèurgentevalguésfalsecaldria escriure l'etiqueta sense l'atribut en absolut:<task-card></task-card>.
Conclusió
En aquesta lliçó has recorregut el catàleg complet de tipus que Lit converteix de sèrie entre atribut i propietat (String, Number, Boolean, Array, Object), t'has aturat en el comportament particular de Boolean amb atributs —presència enfront de contingut textual—, i has après a escriure un converter personalitzat per a tipus que no encaixen en aquest catàleg, aplicant-lo a una nova propietat fechaLimite de <task-card> que ara gestiona objectes Date reals en lloc de simples cadenes de text.
Tot aquest mecanisme de conversió dona per suposat un flux en un sol sentit: de l'atribut cap a la propietat. A la lliçó següent, "Atributs vs Propietats i Reflexió", tancaràs el cercle entenent també el camí invers —quan i per què interessa que un canvi a la propietat es reflecteixi de nou a l'atribut—, i aplicaràs tot el que has après en aquest mòdul per convertir tareas, a <task-list>, en una propietat reactiva de tipus Array, aconseguint per fi que cada <task-card> del tauler mostri les dades de la seva pròpia tasca.
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
