La lliçó 01-01, en presentar Lit davant dels grans frameworks d'aplicació, va deixar una promesa pendent: "aquesta comparació es retomarà amb molt més detall —incloent-hi exemples concrets d'interoperabilitat— en el mòdul d'integració i interoperabilitat". Aquest moment ha arribat. La lliçó anterior va mostrar que un component Lit funciona en qualsevol HTML pla, sense cap framework; aquesta lliçó fa un pas més enllà i examina què passa quan aquest mateix component s'utilitza dins d'una aplicació construïda amb React, Vue o Angular, tres entorns que, cadascun a la seva manera, no van ser dissenyats pensant en Custom Elements des del principi, i que per això presenten friccions concretes —diferents en cada cas— en integrar-los.

Contingut

  1. Per què la interoperabilitat no és automàtica malgrat l'estàndard
  2. React: el problema històric de les propietats no-string
  3. React: esdeveniments personalitzats i la solució amb useRef
  4. <task-card> dins d'un component React d'exemple
  5. Vue: una interoperabilitat quasi directa
  6. Angular: CUSTOM_ELEMENTS_SCHEMA i la resta de matisos
  7. Taula resum de punts de fricció per framework

  1. Per què la interoperabilitat no és automàtica malgrat l'estàndard

La lliçó anterior ha insistit que un component Lit és, al final, un Custom Element estàndard, utilitzable en qualsevol HTML sense dependre de cap framework. Aquesta afirmació continua sent certa dins d'una aplicació React, Vue o Angular: en els tres casos, és perfectament possible escriure l'etiqueta d'un component Lit (<task-card>) directament al marcat d'aquesta aplicació, i el navegador la reconeixerà i la renderitzarà sense cap problema. La fricció no apareix en si l'element es renderitza, sinó en com cada framework decideix comunicar-se amb ell: cadascun dels tres construeix, per damunt del DOM real, un model propi de com es passen dades a un element i com es reben esdeveniments des d'ell, i aquest model propi no sempre coincideix amb la forma en què Custom Elements —i, per tant, Lit— esperen rebre dades i emetre esdeveniments.

Concretament, hi ha dos punts de fricció recurrents, en diferent grau segons el framework: primer, com passar un valor que no sigui una cadena de text simple (el mateix problema de fons ja vist a la lliçó anterior, però ara des del costat del framework que genera el marcat, no des d'HTML escrit a mà); segon, com escoltar un esdeveniment personalitzat (CustomEvent, vist a la lliçó 05-02) que un component Lit despatxi, quan el sistema d'esdeveniments propi del framework espera un format diferent.

  1. React: el problema històric de les propietats no-string

React, fins ben entrada la seva història (i de forma completa només a partir de les versions més recents, amb el suport de propietats de Custom Elements encara consolidant-se en diferents versions), ha tingut un problema conegut en utilitzar Custom Elements dins de JSX: JSX, per defecte, tradueix qualsevol atribut que s'escrigui sobre una etiqueta en minúscula (com task-list, que React tracta com un element natiu del DOM, no com un component propi de React) directament a un atribut HTML, no a una propietat de JavaScript. Això és exactament el mateix problema descrit a la lliçó anterior —la diferència entre atribut i propietat—, però ara amagat darrere d'una sintaxi que, a primera vista, sembla més flexible del que realment és:

// Esto NO establece la propiedad `tareas` como un array real
function Tablero() {
  const tareas = [{ id: 't1', titulo: 'Diseñar', estado: 'pendiente', prioridad: 2 }];
  return <task-list tareas={tareas}></task-list>;
}

Encara que tareas={tareas} sembli, per la seva sintaxi, una assignació de propietat com les que Lit fa servir internament amb el prefix de punt (.tareas="${...}"), React ho tradueix, per a un element que no reconeix com a component propi de React, a un intent d'establir un atribut HTML; i com que un atribut HTML només pot ser una cadena, React acaba cridant internament a alguna cosa equivalent a elemento.setAttribute('tareas', tareas.toString()), que per a un array produeix la cadena literal "[object Object]" o una representació igualment inútil, no l'array real que <task-list> necessita a la seva propietat reactiva tareas.

  1. React: esdeveniments personalitzats i la solució amb useRef

El segon problema històric és simètric, però per a esdeveniments: React reconeix, dins de JSX, atributs amb el prefix on (onClick, onChange) i els tradueix al seu propi sistema intern de gestió d'esdeveniments (el SyntheticEvent de React), però aquest reconeixement està limitat als noms d'esdeveniment que React ja coneix d'antuvi per a elements natius del DOM. Un CustomEvent amb un nom propi de l'aplicació, com tarea-cambiada (despatxat per <task-card> des de la lliçó 05-02), no té cap atribut on equivalent reconegut per React, i escriure <task-card onTareaCambiada={...}> no lliga res, perquè React no sap traduir aquest nom a un addEventListener('tarea-cambiada', ...) real sobre l'element.

La solució establerta per a tots dos problemes —propietats complexes i esdeveniments personalitzats— és la mateixa, i coincideix exactament amb la tècnica ja vista a la lliçó anterior per a HTML pla: obtenir una referència directa a l'element del DOM i manipular-lo amb les API natives, en lloc de dependre de la traducció automàtica de JSX. A React, aquesta referència s'obté amb el hook useRef, i l'assignació de propietats i el registre d'escoltadors d'esdeveniments es fan dins d'un useEffect:

import { useEffect, useRef } from 'react';

function Tablero({ tareas }) {
  const refLista = useRef(null);

  useEffect(() => {
    const elemento = refLista.current;
    elemento.tareas = tareas;

    const manejarCambio = (evento) => {
      console.log('Tarea cambiada:', evento.detail);
    };
    elemento.addEventListener('tarea-cambiada', manejarCambio);

    return () => elemento.removeEventListener('tarea-cambiada', manejarCambio);
  }, [tareas]);

  return <task-list ref={refLista}></task-list>;
}

useEffect s'executa després que React hagi muntat l'element real al DOM, moment en el qual refLista.current ja apunta a la instància concreta de <task-list>; a partir d'aquí, elemento.tareas = tareas és una assignació de propietat de JavaScript corrent, idèntica a la de l'apartat 5 de la lliçó anterior, i elemento.addEventListener('tarea-cambiada', ...) és el mateix mecanisme natiu del DOM utilitzat des de la lliçó 05-02, sense cap traducció intermèdia de React de per mig. La funció retornada per useEffect retira l'escoltador quan el component es desmunta o quan tareas canvia i l'efecte es torna a executar, evitant escoltadors duplicats acumulats en re-renderitzats successius.

És important assenyalar que les versions més recents de React han anat reduint aquesta fricció de forma progressiva, detectant en alguns casos automàticament si una propietat de JSX correspon a una propietat real de l'element en lloc d'un atribut; però, en no ser un comportament garantit ni uniforme entre versions, el patró de useRef més useEffect continua sent la tècnica robusta i explícita que no depèn de quina versió concreta de React s'utilitza.

  1. <task-card> dins d'un component React d'exemple

Amb les dues peces anteriors, així es veuria un component React que embolcalla <task-card> per exposar-la amb una API més còmoda cap a la resta de l'aplicació React que la utilitza:

// src/components/TarjetaTarea.jsx
import { useEffect, useRef } from 'react';
import './task-card.js'; // registra <task-card> mediante customElements.define

function TarjetaTarea({ tarea, onCambio }) {
  const refTarjeta = useRef(null);

  useEffect(() => {
    const elemento = refTarjeta.current;
    elemento.titulo = tarea.titulo;
    elemento.estado = tarea.estado;
    elemento.prioridad = tarea.prioridad;

    const manejarCambio = (evento) => onCambio(evento.detail);
    elemento.addEventListener('tarea-cambiada', manejarCambio);
    return () => elemento.removeEventListener('tarea-cambiada', manejarCambio);
  }, [tarea, onCambio]);

  return <task-card ref={refTarjeta}></task-card>;
}

export default TarjetaTarea;

TarjetaTarea és un component React perfectament normal des del punt de vista de la resta de l'aplicació —rep props (tarea, onCambio) amb la sintaxi habitual de React, i les tradueix internament cap a les propietats i esdeveniments reals de <task-card>—, actuant com una capa d'adaptació entre el model de props de React i el model de propietats i CustomEvent de Lit. Cal notar que titulo, estado i prioridad es podrien, en aquest cas concret, passar igualment com a atributs JSX normals (titulo={tarea.titulo}), perquè són cadenes o números simples i no pateixen el problema de l'apartat 2; s'assignen aquí com a propietats dins del mateix useEffect només per uniformitat amb la resta del patró, no perquè sigui estrictament necessari per a aquests tres camps concrets.

  1. Vue: una interoperabilitat quasi directa

Vue resol la major part d'aquesta fricció de forma nativa, sense necessitat de cap patró equivalent a useRef més useEffect per als casos habituals. La plantilla d'un component Vue d'un fitxer .vue reconeix directament la sintaxi de binding (:propiedad="valor") sobre qualsevol etiqueta, inclosa una etiqueta de Custom Element, i la tradueix a una assignació de propietat real del DOM quan el valor no és una cadena simple, exactament el comportament que React necessita un patró manual per aconseguir:

<template>
  <task-list :tareas="tareas" @tarea-cambiada="manejarCambio"></task-list>
</template>

<script setup>
import { ref } from 'vue';
import './task-list.js';

const tareas = ref([
  { id: 't1', titulo: 'Diseñar la base de datos', estado: 'hecha', prioridad: 2 },
]);

function manejarCambio(evento) {
  console.log('Tarea cambiada:', evento.detail);
}
</script>

:tareas="tareas" estableix la propietat real tareas sobre l'element (no un atribut de text), perquè Vue, a diferència de React amb JSX, decideix en temps d'execució si el valor s'ha d'aplicar com a propietat o com a atribut segons el tipus del valor i segons si el nom coincideix amb una propietat coneguda de l'element del DOM; i @tarea-cambiada="manejarCambio" registra un escoltador d'esdeveniment natiu (addEventListener('tarea-cambiada', ...)) sense necessitar que tarea-cambiada sigui un nom d'esdeveniment que Vue conegui d'antuvi, a diferència de la limitació assenyalada per a React a l'apartat 3. L'únic ajust que a vegades cal declarar explícitament és informar Vue de quines etiquetes són Custom Elements i no components propis de Vue mal escrits, mitjançant l'opció compilerOptions.isCustomElement a la configuració del projecte (normalment a vite.config.js), perquè Vue no emeti un avís de "component desconegut" en trobar-se amb <task-list> a la plantilla.

  1. Angular: CUSTOM_ELEMENTS_SCHEMA i la resta de matisos

Angular ocupa una posició intermèdia: el seu sistema de plantilles és més estricte que el de Vue a l'hora d'acceptar etiquetes i atributs que no reconeix d'antuvi, precisament perquè Angular valida en temps de compilació quins elements i propietats existeixen, per poder oferir comprovació de tipus i detectar errors abans d'executar l'aplicació. Per defecte, Angular rebutja compilar una plantilla que contingui una etiqueta desconeguda com <task-card>, amb un error del tipus "'task-card' is not a known element". La solució consisteix a declarar explícitament, al mòdul o component corresponent, que es permet l'ús de Custom Elements no reconeguts pel propi Angular:

// tablero.component.ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import './task-card.js';

@Component({
  selector: 'app-tablero',
  template: `
    <task-card
      [titulo]="tarea.titulo"
      [estado]="tarea.estado"
      (tarea-cambiada)="manejarCambio($event)"
    ></task-card>
  `,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class TableroComponent {
  tarea = { titulo: 'Revisar propuesta', estado: 'progreso' };

  manejarCambio(evento: CustomEvent) {
    console.log('Tarea cambiada:', evento.detail);
  }
}

schemas: [CUSTOM_ELEMENTS_SCHEMA] desactiva, per a aquest component en concret, la comprovació estricta d'etiquetes i propietats desconegudes, permetent a Angular compilar la plantilla sense queixar-se de <task-card> ni dels seus atributs. Un cop afegit aquest esquema, la sintaxi de binding de propietats d'Angular ([titulo]="...", amb claudàtors) sí que tradueix correctament a una assignació de propietat del DOM, de forma similar a Vue; i la sintaxi d'esdeveniments ((tarea-cambiada)="...", amb parèntesis) sí que escolta correctament un CustomEvent amb aquest nom exacte, sense la limitació de noms predefinits que afecta React. El cost d'aquest enfocament és, precisament, la pèrdua de la comprovació de tipus que Angular ofereix per als seus propis components: amb CUSTOM_ELEMENTS_SCHEMA actiu, Angular ja no pot avisar en temps de compilació si s'escriu, per error, [titulol] en lloc de [titulo], i aquest error només es manifestaria en temps d'execució, amb la propietat titulol simplement ignorada per <task-card>.

  1. Taula resum de punts de fricció per framework

Aspecte React Vue Angular
Passar una propietat complexa (array, objecte) Requereix useRef + useEffect; JSX la tractaria com a atribut de text Directe amb :propiedad="valor"; Vue decideix propietat o atribut automàticament Directe amb [propiedad]="valor", un cop declarat CUSTOM_ELEMENTS_SCHEMA
Escoltar un CustomEvent amb nom propi Requereix addEventListener manual dins de useEffect; no hi ha atribut on reconegut Directe amb @nombre-evento="manejador" Directe amb (nombre-evento)="manejador($event)", un cop declarat l'esquema
Configuració prèvia necessària Cap especial, però exigeix el patró manual a cada ús compilerOptions.isCustomElement per evitar avisos schemas: [CUSTOM_ELEMENTS_SCHEMA] a cada component que utilitzi Custom Elements
Cost de la solució Codi repetitiu (una capa d'adaptació per component embolcallat) Pràcticament cap per als casos habituals Pèrdua de comprovació de tipus estricta en aquesta plantilla
Tendència Millorant en versions recents, però sense garantia uniforme Ja resolt de forma nativa des de fa temps Estable, requereix el pas explícit de l'esquema

El patró de fons, visible a les tres columnes, és sempre el mateix: com més un framework decideix inspeccionar i validar d'antuvi les etiquetes i atributs d'una plantilla (Angular, i en menor mesura React amb JSX), més fricció apareix en introduir un element que aquest framework no pot conèixer d'antuvi per no formar part del seu propi ecosistema de components; com més un framework decideix delegar en temps d'execució la decisió de si una cosa és una propietat o un atribut (Vue, de forma força directa), menys fricció hi ha. Cap dels tres frameworks impedeix, en cap cas, utilitzar un component Lit; la diferència està en quant codi d'adaptació addicional cal escriure per aconseguir-ho amb comoditat.

Errors Habituals i Consells

  • Passar un array o un objecte per atribut JSX a React esperant que funcioni com a propietat: com s'ha explicat a l'apartat 2, JSX tradueix per defecte a atribut HTML sobre una etiqueta que React no reconeix com el seu propi component; cal el patró de useRef més useEffect de l'apartat 3 per a qualsevol valor que no sigui una cadena o número simple.
  • Oblidar declarar CUSTOM_ELEMENTS_SCHEMA a Angular: sense ell, l'aplicació ni tan sols compila, amb un error d'"element desconegut" assenyalant directament l'etiqueta del component Lit; és un pas obligatori, no opcional, en qualsevol component Angular que utilitzi Custom Elements de tercers o propis.
  • Suposar que Vue necessita el mateix patró manual que React: com s'ha vist a l'apartat 5, Vue resol automàticament la majoria de casos amb la seva sintaxi de binding habitual; replicar el patró de useRef a Vue no és necessari i només afegeix codi redundant.
  • No retirar els escoltadors d'esdeveniment afegits manualment amb addEventListener: en el patró de React de l'apartat 3, oblidar la funció de neteja retornada per useEffect acumula escoltadors duplicats a cada re-renderitzat que torni a executar l'efecte, un error de gestió de memòria fàcil d'introduir i difícil de detectar sense eines de perfilatge.

Exercicis

  1. Reescriu el component TarjetaTarea de React de l'apartat 4 utilitzant la biblioteca oficial @lit/react, que ofereix una funció createComponent(...) capaç de generar automàticament un component React embolcallador a partir de la classe d'un component Lit, sense necessitat d'escriure a mà el useRef i el useEffect. (Pista: investiga la signatura de createComponent a la documentació de @lit/react; no cal memoritzar-la, n'hi ha prou de raonar quina informació necessitaria rebre per poder generar l'embolcall automàticament: la classe de l'element, el seu nom d'etiqueta, i quins esdeveniments ha de traduir a props de React.)
  2. Explica, basant-te en l'apartat 6, per què CUSTOM_ELEMENTS_SCHEMA redueix la seguretat de tipus d'Angular, i quina comprovació en concret deixa de fer el compilador d'Angular sobre una plantilla que ho declari.
  3. Un company d'equip, treballant amb Vue, es sorprèn que :tareas="tareas" funcioni directament sense cap pas addicional, mentre que a React va necessitar el patró complet de useRef. Explica la diferència de fons entre tots dos frameworks que provoca aquesta experiència diferent, basant-te en els apartats 2 i 5.

Solucions

  1. createComponent de @lit/react necessita, com a mínim, tres peces d'informació per generar l'embolcall automàticament: la biblioteca de React (react, per poder utilitzar els seus hooks internament sense que el projecte els hagi de declarar), la classe del propi component Lit (TaskCard, per poder inspeccionar les seves propietats reactives declarades a static properties), i el nom de l'etiqueta ja registrada ('task-card'). Amb aquestes tres peces, createComponent pot generar automàticament, per dins, exactament el mateix patró de useRef i useEffect escrit a mà a l'apartat 4 —assignar cada propietat reactiva coneguda com a propietat de JavaScript real, no com a atribut— i, per als esdeveniments, acceptar un mapa addicional (alguna cosa com events: { onTareaCambiada: 'tarea-cambiada' }) que tradueix cada CustomEvent a una prop de tipus funció amb la convenció de noms on... habitual a React, sense que el desenvolupador hagi d'escriure addEventListener a mà en cada component embolcallat.
  2. CUSTOM_ELEMENTS_SCHEMA desactiva la validació que Angular fa, en temps de compilació, sobre si una etiqueta existeix com a component o directiva coneguda per Angular, i sobre si cada [propiedad] o (evento) escrit en una plantilla correspon realment a una entrada declarada d'aquesta etiqueta (una @Input() o un @Output(), en la terminologia d'Angular). Sense l'esquema, Angular pot avisar en temps de compilació d'un error d'escriptura com [titulol] en lloc de [titulo]; amb l'esquema actiu, aquesta mateixa comprovació queda desactivada per a tota la plantilla del component, i un error d'aquest tipus només es manifestaria en temps d'execució, amb la propietat mal escrita simplement ignorada sense cap avís.
  3. La diferència de fons és el moment i el criteri en què cada framework decideix si un valor s'ha d'aplicar com a propietat de JavaScript o com a atribut HTML. React amb JSX aplica una regla fixa i sintàctica: sobre una etiqueta que no reconeix com el seu propi component, qualsevol atribut de JSX es tradueix per defecte a atribut HTML, sense inspeccionar el tipus del valor ni l'element real. Vue, en canvi, decideix en temps d'execució, inspeccionant el propi element del DOM: si existeix una propietat d'aquest nom a l'element (cosa que pot comprovar dinàmicament, ja que tareas és una propietat reactiva real declarada per Lit a static properties i exposada com a propietat d'instància del Custom Element), Vue l'assigna com a propietat; si no, recorre a un atribut. Aquesta inspecció dinàmica és precisament el que permet a Vue resoldre correctament casos com :tareas="tareas" sense cap patró manual addicional.

Conclusió

Aquesta lliçó ha complert la promesa de la lliçó 01-01: una comparació pràctica, amb exemples concrets, de com s'integra un component Lit dins de React, Vue i Angular, mostrant que el problema de fons és sempre el mateix —com passar valors no-cadena i com escoltar esdeveniments personalitzats— però que cada framework el resol, o obliga a resoldre'l manualment, de forma diferent segons quant validi d'antuvi les seves pròpies plantilles. Amb la interoperabilitat cap a altres frameworks ja coberta, queda un escenari d'integració de naturalesa diferent: què passa quan un component Lit no es renderitza al navegador de l'usuari final, sinó en un servidor, abans que la pàgina arribi tan sols a carregar-se. La lliçó següent presenta @lit-labs/ssr i el problema del renderitzat al servidor per a Web Components.

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