Planteamiento y alcance
Debes entregar un elemento personalizado <account-summary> para hosts renderizados mediante React, Vue o plantillas del servidor. Muestra el estado de la cuenta, reacciona a un atributo status, despacha un evento público y libera las suscripciones cuando se elimina del documento. Los estilos deben permanecer contenidos sin perder la semántica de teclado ni de tecnologías de asistencia.
Asume primero un elemento personalizado autónomo (autonomous custom element). Si el requisito es extender un button nativo u otro elemento integrado, evalúa los elementos integrados personalizados (customized built-in elements) por separado, ya que el soporte del navegador cambia el diseño. La entrevista evalúa los límites del ciclo de vida y la propiedad de los recursos, no la sintaxis de componentes de un framework.
Qué está evaluando el entrevistador
- Si puedes asignar responsabilidades distintas a constructor, connectedCallback, disconnectedCallback y a la observación de atributos.
- Si manejas reconexiones, traslados, mejoras progresivas (upgrades) y atributos iniciales en lugar de asumir que cada callback se ejecuta una sola vez.
- Si conectas la encapsulación de Shadow DOM con la accesibilidad, la propagación de eventos y la integración con el host.
- Si puedes proponer un registro idempotente, compatibilidad, limpieza y pruebas.
Recitar los nombres de los callbacks no es suficiente. Una respuesta sólida define primero la propiedad de los recursos y luego explica cómo interactúan la conexión, los atributos y el estado de renderizado.
Preguntas para aclarar primero
- ¿El elemento debe extender un elemento semántico nativo? De ser así, la sintaxis
isy el soporte del navegador afectan la elección. - ¿Las entradas son cadenas de texto o los consumidores deben pasar objetos y callbacks? Los valores complejos necesitan un contrato explícito de propiedades o métodos.
- ¿El estado debe sobrevivir a un traslado dentro del mismo documento? Esto determina si se debe evaluar
connectedMoveCallbacko preservar el estado explícitamente. - ¿Los eventos deben cruzar el límite del Shadow DOM? Acuerda
bubbles,composedy el payload antes de elegir la API de eventos.
Estructura de respuesta en 30 segundos
“Separo la definición, la conexión, la sincronización de atributos, el renderizado y la limpieza. El constructor solo inicializa campos ligeros y no depende de nodos hijos ni del documento externo. connectedCallback establece suscripciones y renderiza de forma idempotente; disconnectedCallback libera listeners, temporizadores y observadores. Solo observo los atributos necesarios, normalizo sus valores de cadena en attributeChangedCallback y canalizo las actualizaciones a través de una única ruta de renderizado. Shadow DOM contiene los estilos de implementación, mientras que la semántica pública, el foco y los eventos permanecen como parte del contrato probado del host. Concluyo con pruebas para reconexiones, traslados, atributos iniciales, eliminación, upgrades y navegadores no compatibles”.
Análisis paso a paso
1. Definir la propiedad del estado y de los recursos
Rastrea el estado conectado, suscrito y renderizado por separado. El constructor no debe leer el DOM exterior ni iniciar trabajo de red: un navegador puede construir un elemento antes de conectarlo a un documento.
class AccountSummary extends HTMLElement {
static observedAttributes = ["status"];
#connected = false;
#unsubscribe = null;
#root;
constructor() {
super();
this.#root = this.attachShadow({ mode: "open" });
}
}2. Hacer que connectedCallback sea seguro de repetir
Un elemento puede eliminarse e insertarse de nuevo, por lo que connectedCallback no debe registrar otro listener incondicionalmente. Verifica el estado de conexión, establece las suscripciones y haz que el renderizado sea seguro de invocar repetidamente.
connectedCallback() {
if (this.#connected) return;
this.#connected = true;
this.#unsubscribe = accountStore.subscribe(() => this.#render());
this.#render();
}3. Liberar cada efecto secundario en disconnectedCallback
Los event listeners, setInterval, ResizeObserver, AbortController y las suscripciones a stores necesitan propietarios explícitos. Limpia las referencias después del saneamiento para que una conexión posterior cree exactamente un único conjunto nuevo de recursos.
disconnectedCallback() {
this.#unsubscribe?.();
this.#unsubscribe = null;
this.#connected = false;
}Los patrones de traslado más antiguos pueden desencadenar callbacks de desconexión y conexión en secuencia. Si preservar el estado a través de traslados es importante, evalúa connectedMoveCallback donde esté soportado, o reconstruye a partir de atributos y estado duradero en lugar de depender del orden de los callbacks.
4. Observar atributos y separar propiedades
observedAttributes solo debe listar los valores que necesitan sincronización. attributeChangedCallback puede ejecutarse para un atributo inicial durante el parseo, por lo que “cambiado” no significa necesariamente que un usuario lo acaba de editar. Maneja los valores iniciales y posteriores a través de la misma ruta y evita escribir el mismo atributo de vuelta desde su callback.
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) return;
if (name === "status") this.#renderStatus(newValue ?? "unknown");
}Los atributos de tipo cadena funcionan bien para el HTML declarativo. Pasa arreglos, objetos y callbacks a través de propiedades o métodos documentados en lugar de tratar cadenas JSON arbitrarias como un protocolo sin límites.
5. Diseñar Shadow DOM y el contrato con el host
Shadow DOM aísla el CSS interno y las consultas de DOM, pero no suministra semántica automáticamente. Usa roles correctos, nombres accesibles, orden de foco y visibilidad. Si el host debe participar en el diseño (layout), expón un contrato acotado a través de :host, slots o propiedades personalizadas de CSS.
:host { display: block; }
:host([hidden]) { display: none; }Para los eventos que los hosts deben observar, establece bubbles y composed deliberadamente y mantén el payload estable. Cruzar un límite no requiere exponer nodos internos.
6. Manejar el registro, las actualizaciones (upgrades) y la compatibilidad
Un nombre solo puede definirse una vez en un registro global. Las librerías compartidas deben usar un prefijo de espacio de nombres y verificar customElements.get(name) antes de definir; no ocultes colisiones capturando una excepción. Los elementos parseados antes del registro se actualizan (upgrade) después de la definición, por lo que la implementación debe soportar ese orden.
const name = "account-summary";
if (!customElements.get(name)) {
customElements.define(name, AccountSummary);
}Los elementos autónomos suelen ser más fáciles de desplegar en distintos navegadores. MDN documenta las limitaciones de Safari para elementos integrados personalizados; si extender un elemento nativo es obligatorio, verifica la matriz de navegadores objetivo y prepara una alternativa (fallback).
Respuesta de ejemplo de alta calidad
Primero escribiría una tabla de propiedad: el constructor crea el Shadow Root y los campos predeterminados, connectedCallback se suscribe y renderiza, disconnectedCallback cancela las suscripciones y attributeChangedCallback maneja solo la configuración declarativa observada. Cada fase es repetible o se puede omitir de manera segura; “se ejecuta una vez” no se trata como una garantía del ciclo de vida.
Elegiría un autonomous custom element y protegería el registro con customElements.get. Shadow DOM contiene los estilos de implementación, mientras que el componente aún expone un nombre accesible, comportamiento de foco y un contrato de eventos. Los eventos que cruzan límites usan configuraciones explícitas de bubbles y composed con un payload restringido. Los atributos transportan configuración declarativa primitiva; los objetos complejos usan propiedades o métodos.
La verificación cubre upgrades de parseo previo a la definición, atributos iniciales, reconexiones, traslados, limpieza, eventos a través de límites, comportamiento del teclado y semántica de lectores de pantalla. Si se requieren elementos integrados personalizados, primero validaría la matriz de navegadores y luego elegiría esa ruta solo si el costo de compatibilidad es aceptable.
Errores comunes y mejoras
- Error → Leer nodos hijos o iniciar peticiones en el constructor → Por qué falla → El elemento puede no estar conectado aún → Mejora → Mover el trabajo del documento y de la red a connectedCallback y vincular las peticiones a un AbortController.
- Error → Agregar listeners en cada connectedCallback → Por qué falla → Las reconexiones duplican el trabajo y tienen fugas de memoria → Mejora → Mantener una referencia de suscripción y hacer que la configuración/limpieza sean pares idempotentes.
- Error → Llamar incondicionalmente a setAttribute desde el callback de atributos → Por qué falla → Puede formar un bucle de callbacks, y el parseo de atributos iniciales también invoca el callback → Mejora → Comparar valores antiguos y nuevos, normalizar una sola vez y evitar escribir el mismo atributo.
- Error → Tratar Shadow DOM como una solución de accesibilidad → Por qué falla → La encapsulación no crea nombres, foco ni semántica → Mejora → Probar el contrato público con flujos de teclado y tecnologías de asistencia.
Preguntas de seguimiento y respuestas
El elemento se parsea antes de definirse. ¿Cómo se evita un parpadeo (flash)?
Trata el elemento no definido como un estado actualizable y proporciona contenido de respaldo legible o un estado de carga. El navegador actualiza (upgrade) las instancias después del registro. Si el código debe esperar, el host puede esperar con await a customElements.whenDefined("account-summary"), pero la página completa no debe bloquearse esperando el script del componente.
¿Deberían colocarse objetos complejos en los atributos?
Normalmente no. Los atributos son cadenas y se adaptan a configuraciones declarativas serializables; los objetos y los callbacks deben usar propiedades o métodos documentados con un comportamiento claro antes y después de la conexión. Si se requiere serialización, especifica versión, tamaño y manejo de errores de parseo.
¿Por qué un host no recibe un clic de un botón dentro de Shadow DOM?
La propagación a través de límites depende de composed; el burbujeo ascendente depende de bubbles. Despacha un evento semántico estable con ambas opciones elegidas explícitamente. Fuera del límite, el objetivo puede redirigirse (retargeted) al shadow host, por lo que los hosts no deben depender de nodos internos.
¿Cómo demuestras que las reconexiones no tienen fugas de memoria?
Inserta y elimina la misma instancia repetidamente, cuenta suscripciones, entregas de eventos e identificadores de temporizadores, y luego usa instantáneas de memoria para verificar que los nodos antiguos puedan ser recolectados por el recolector de basura. Cubre callbacks de atributos iniciales, traslados y upgrades de parseo previo a la definición; el primer renderizado por sí solo demuestra poco.