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

  1. El problema de fons: HTML només entén text
  2. Els tipus suportats de sèrie: String, Number, Boolean, Array, Object
  3. El problema clàssic de Boolean amb atributs
  4. Quan cal un conversor personalitzat
  5. Escrivint un converter a mida
  6. Aplicant el conversor a fechaLimite a <task-card>

  1. 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ó).

  1. Els tipus suportats de sèrie: String, Number, Boolean, Array, Object

Lit 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.

  1. El problema clàssic de Boolean amb atributs

El 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.

  1. 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 String funcionaria a mitges: l'atribut funcionaria, però la propietat JavaScript seria simplement una cadena de text, sense cap de les capacitats d'un objecte Date (comparar dates, calcular quants dies falten, formatar de maneres diferents).
  • Declarar-la com a Object tampoc encaixa bé: un objecte Date de JavaScript no es serialitza de manera útil amb JSON.stringify (produeix una cadena en format ISO dins de cometes, però en fer JSON.parse de tornada no es reconstrueix automàticament com un objecte Date, 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.

  1. Escrivint un converter a mida

Un 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.

  1. Aplicant el conversor a 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 valgui false: com es va explicar a l'apartat 3, amb type: Boolean el que importa és la presència o absència de l'atribut, no el seu contingut de text; perquè valgui false des d'HTML, l'atribut s'ha d'ometre per complet.
  • Intentar declarar type: Date esperant que Lit ho suporti de sèrie: Date no és un dels cinc tipus reconeguts automàticament (apartat 2); si es declara { type: Date }, Lit el tractarà de manera equivalent a String sense cap tipus de conversió especial, i this.fechaLimite seria simplement el text de l'atribut, no un objecte Date. Per a tipus no suportats de sèrie cal un converter personalitzat, 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és if (!valorDelAtributo) al principi, cridar new Date(null) o new Date(undefined) produiria una data no vàlida (Invalid Date) en lloc de fallar de manera clara o retornar null, la qual cosa pot provocar errors confusos més endavant a la plantilla.
  • Passar JSON mal formatat a un atribut de tipus Array o Object: 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

  1. Sense utilitzar cap conversor personalitzat, declara a <task-card> una propietat etiquetas de tipus Array, 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 amb elemento.etiquetas = ['cliente', 'urgente'], que la plantilla s'actualitza.
  2. Recupera el conversor conversorDeFecha de l'apartat 5 i afegeix una comprovació addicional a fromAttribute que, si el new Date(...) resultant fos no vàlid (es pot comprovar amb Number.isNaN(fecha.getTime())), retorni null en lloc d'una data no vàlida.
  3. 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è urgente valgués false.

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];
  },
};
  1. 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 que urgente="false" continua tenint l'atribut urgente present (amb el text "false" a dins, però present al cap i a la fi), Lit l'interpreta com a true, igual que passa amb atributs natius com disabled. Perquè urgente valgués false caldria 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

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