Domina el ciclo de vida de los elementos personalizados, la encapsulación del shadow DOM y la proyección de contenido mediante slots.
Abrir esta lección en KodokonLos elementos personalizados (custom elements) extienden el vocabulario de HTML: cualquier clase que herede de HTMLElement puede registrarse en el CustomElementRegistry, siempre que su nombre contenga un guion (user-badge, nunca userbadge). Punto sutil: si la etiqueta aparece en el documento antes de que se llame a customElements.define, primero se crea como un elemento desconocido y luego se actualiza (upgrade) en su sitio - el constructor se invoca entonces a posteriori sobre un nodo ya presente en el árbol.
class UserBadge extends HTMLElement {
static observedAttributes = ["name"];
connectedCallback() {
this.render();
}
attributeChangedCallback() {
this.render();
}
render() {
this.textContent = this.getAttribute("name");
}
}
customElements.define("user-badge", UserBadge);El ciclo de vida esconde algunas trampas: attributeChangedCallback solo se dispara para los atributos listados en observedAttributes, y durante una actualización se llama para cada atributo ya presente antes de connectedCallback. La especificación prohíbe que el constructor inspeccione atributos e hijos, o que añada alguno: en el momento en que se ejecuta (vía createElement o clonación), el elemento puede estar completamente vacío.
<template id="card-tpl">
<style>
:host { display: block; padding: 1rem; }
::slotted(h2) { margin: 0; }
</style>
<slot name="title"></slot>
<slot></slot>
</template>attachShadow crea un subárbol encapsulado: los estilos no se filtran fuera de él, los selectores de la página no llegan a su interior - solo las propiedades CSS heredadas (color, font) y las propiedades personalizadas cruzan la frontera. El contenido de un <template> vive en un DocumentFragment accesible mediante .content: sus scripts no se ejecutan y sus imágenes no se cargan hasta que lo clonas en el documento. Los nodos colocados en el light DOM se proyectan en los elementos <slot>: permanecen físicamente en el light DOM, y el evento slotchange te notifica de cualquier cambio en la asignación.
class InfoCard extends HTMLElement {
constructor() {
super();
const tpl = document.getElementById("card-tpl");
this.attachShadow({ mode: "open" })
.appendChild(tpl.content.cloneNode(true));
}
}
customElements.define("info-card", InfoCard);Dos extensiones merecen tu atención. Elementos personalizados asociados a formularios (form-associated custom elements): con static formAssociated = true y attachInternals(), tu componente participa plenamente en los formularios (valor enviado, validación nativa, estados :invalid). Y el shadow DOM declarativo: <template shadowrootmode=open> adjunta un shadow root en el momento del análisis, sin JavaScript, lo que hace que los web components sean compatibles con el renderizado del lado del servidor.
class UserBadge extends HTMLElement {
connectedCallback() {
this.textContent = this.getAttribute("name");
}
}
customElements.define("___", UserBadge);<slot>?