Manipule o click do botão interno, depois dispare um CustomEvent nomeado a partir do host do componente. A página consumidora escuta nesse host e lê event.detail. Este guia cria uma demonstração completa de navegador local em um Mac e explica quais opções de evento cada ouvinte precisa.
Defina a notificação que seu consumidor precisa
Para um componente web JavaScript simples, o consumidor é a página ou componente envolvente que usa seu elemento personalizado. Aqui, a tarefa concreta é permitir que esse consumidor reaja quando alguém ativa um botão interno, sem consultar o shadow DOM do componente.
O exemplo usa um bloco sintético rotulado Tile A. Seu evento público é tile-activate, transportando um itemId e uma ativação count. CustomEvent fornece o campo detail para esses dados de aplicação; o nome do evento diferencia maiúsculas de minúsculas. Tanto o código de envio quanto o ouvinte devem usar exatamente o mesmo nome. Documentação do construtor CustomEvent.
Trate essas escolhas como um pequeno contrato de interface:
- Nome:
tile-activate. - Significado: o botão interno foi ativado e a contagem atualizada.
- Carga:
detail.itemIdidentifica o bloco;detail.countcontém sua contagem atualizada. - Origem: o host do elemento personalizado envia a notificação.
- Entrega: consumidores diretos e ouvintes ancestrais podem recebê-la, inclusive através de limites de shadow envolventes.
Estas são escolhas de design para este exemplo. Um evento útil descreve a ação que o consumidor considera importante. Isso permite que o componente altere sua marcação interna mais tarde, mantendo a mesma interface de notificação.
Criar e abrir o exemplo completo no seu Mac
Você precisa de um navegador desktop atual e um editor de texto simples. Nenhuma instalação de pacote ou etapa de build é necessária para este exemplo de arquivo único. Ele usa um elemento personalizado autônomo que estende HTMLElement, o padrão de plataforma documentado na seção de elementos personalizados do padrão HTML.
No TextEdit, escolha Arquivo > Novo, depois Formato > Tornar Texto Simples. Cole o documento completo abaixo. Escolha Arquivo > Salvar, dê o nome component-click.html e selecione Usar .html se solicitado. Estas são as etapas documentadas pela Apple para criar um arquivo HTML no TextEdit. Trabalhar com documentos HTML no TextEdit no Mac.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Component click notification</title>
</head>
<body>
<h1>Component click notification</h1>
<section id="demo">
<action-tile id="tile" item-id="tile-a"></action-tile>
<p id="direct-status">Direct listener: waiting</p>
<p id="parent-status">Parent listener: waiting</p>
</section>
<script>
class ActionTile extends HTMLElement {
constructor() {
super();
this._count = 0;
this._button = null;
this._root = this.attachShadow({ mode: "open" });
this._handleClick = () => {
this._count += 1;
this.dispatchEvent(new CustomEvent("tile-activate", {
detail: {
itemId: this.getAttribute("item-id"),
count: this._count
},
bubbles: true,
composed: true
}));
};
}
connectedCallback() {
if (!this._button) {
this._button = document.createElement("button");
this._button.type = "button";
this._button.textContent = "Activate Tile A";
this._root.append(this._button);
}
this._button.addEventListener("click", this._handleClick);
}
disconnectedCallback() {
this._button.removeEventListener("click", this._handleClick);
}
}
customElements.define("action-tile", ActionTile);
const tile = document.getElementById("tile");
const demo = document.getElementById("demo");
const directStatus = document.getElementById("direct-status");
const parentStatus = document.getElementById("parent-status");
tile.addEventListener("tile-activate", (event) => {
const { itemId, count } = event.detail;
directStatus.textContent =
`Direct listener: ${itemId}, activation ${count}`;
});
demo.addEventListener("tile-activate", (event) => {
const { itemId, count } = event.detail;
parentStatus.textContent =
`Parent listener: ${itemId}, activation ${count}`;
});
</script>
</body>
</html>
Abra o arquivo salvo no seu navegador e clique em Activate Tile A. O resultado esperado inicialmente é Direct listener: tile-a, activation 1 e Parent listener: tile-a, activation 1. Cada ativação subsequente aumenta ambas as contagens exibidas em um. Estes são resultados esperados do exemplo, não resultados de testes de navegador relatados.
A sequência importante está visível no código: atualizar o estado do componente, criar a notificação, despachá-la a partir de this e permitir que o consumidor atualize sua própria interface. dispatchEvent() invoca os listeners aplicáveis de forma síncrona antes de retornar, para que os consumidores vejam a contagem atualizada durante seus manipuladores. documentação de dispatchEvent.
Escolha as opções corretas de bubbling e limite de shadow
O alvo do despacho e a localização do listener determinam quais opções importam. Um listener anexado diretamente ao host recebe um evento despachado nesse host mesmo quando bubbles é false. Um listener ancestral comum precisa de bubbling. Cruzar um limite de shadow também requer composed. Os padrões subjacentes para bubbles, composed e cancelable são todos false. DOM Standard: interface Event.
A seguinte tabela de decisão é derivada dessas regras de propagação. Ela assume listeners comuns registrados sem capture e nenhum manipulador interrompendo a propagação.
| Onde o evento personalizado começa | Onde o consumidor escuta | Configuração para essa rota |
|---|---|---|
| Host do componente | Mesmo host | Nenhuma flag precisa ser true |
| Host do componente | Ancestral comum na mesma árvore | bubbles: true |
| Botão interno shadow | Ancestral comum fora dessa raiz shadow | bubbles: true, composed: true |
| Host do componente dentro de outra árvore shadow | Ancestral comum fora da raiz shadow envolvente | bubbles: true, composed: true |
Na demonstração, this.dispatchEvent(...) inicia a notificação no host. Portanto, não precisa escapar da raiz shadow do próprio componente. composed: true torna a notificação utilizável através da fronteira shadow de um componente envolvente se este bloco for aninhado posteriormente. Esse alcance mais amplo deve ser uma parte deliberada da interface pública. Documentação de eventos compostos.
Para um experimento focado, altere apenas bubbles: true para bubbles: false, salve e recarregue a página. Na ativação, o status direto deve ser atualizado enquanto o status do pai permanece Parent listener: waiting. Restaure o bubbling depois. Alterar apenas composed para false deve manter ambos os listeners funcionando neste documento específico porque nenhuma rota cruza uma fronteira shadow.
Preservar comportamento de inicialização e reconexão
A entrega de eventos pode parecer correta inicialmente, mas produzir notificações duplicadas depois que um componente é movido ou reconectado. Mantenha as responsabilidades de criação, conexão e limpeza explícitas.
O construtor chama super() primeiro, inicializa o estado e anexa uma shadow root. A renderização ocorre em connectedCallback(), protegida pela referência do botão existente para que seja executada uma única vez. Essas escolhas seguem os requisitos do construtor do HTML Standard e sua orientação para proteger a inicialização que não deve se repetir quando os callbacks de conexão são executados novamente. Requisitos para construtores e reações de elementos personalizados.
O componente registra seu listener de clique interno quando conectado e o remove quando desconectado. Na reconexão, ele reutiliza o botão existente e preserva _count. Os callbacks de conexão e desconexão são ganchos de ciclo de vida invocados quando os elementos entram e saem do documento. Usando elementos personalizados.
A função _handleClick armazenada importa: a remoção deve identificar a mesma função usada durante o registro, com a mesma configuração de captura. Criar uma nova arrow function na chamada de remoção não identificaria o listener registrado. Documentação do removeEventListener.
Se você adicionar listeners a window, document ou a outro alvo externo em um componente maior, dê a esses listeners uma limpeza igualmente explícita. Mantenha as mudanças de ciclo de vida separadas do significado da notificação para que a reconexão do elemento não anuncie acidentalmente uma ação do usuário.
Diagnosticar notificações ausentes ou repetidas
Use as duas linhas de status visíveis para localizar a falha antes de alterar as sinalizações de evento. Elas distinguem um problema com a notificação do componente de um problema com a entrega do ancestral.
- Nenhum status muda: compare o nome do evento no disparo e no registro, depois verifique se a definição do elemento personalizado e o manipulador do botão são executados. Neste documento, os ouvintes do consumidor são registrados antes de qualquer ativação do usuário.
- Apenas o status direto muda: verifique
bubbles. Também inspecione qualquer manipulador intermediário que interrompa a propagação. - A entrega falha apenas quando aninhada em outro componente: identifique o limite de sombra nessa rota e verifique
composed. - Contagens saltam após a reconexão: inspecione o registro repetido de ouvintes, especialmente novas funções anônimas criadas cada vez que o elemento é conectado.
Funções anônimas repetidas são objetos de ouvinte separados, mesmo que seu texto de origem seja idêntico. Manter uma referência de função estável torna o registro e a limpeza mais fáceis de entender. addEventListener() também distingue a opção de captura do ouvinte, que afeta quando ele é executado. documentação do addEventListener.
Um click nativo já pode alcançar fora de uma shadow tree porque cliques despachados pelo navegador são compostos. Um evento personalizado separado é útil quando o consumidor precisa de uma ação de componente nomeada e uma carga útil definida. Evite lidar com ambas as notificações como a mesma ação da aplicação, a menos que essa duplicação seja intencional. Documentação de eventos compostos.
Por fim, mantenha a notificação e a permissão para prosseguir distintas no design do seu evento. Este exemplo incrementa a contagem antes do despacho e deixa o evento não cancelável. Um consumidor que chama preventDefault() não pode desfazer essa atualização. Uma solicitação cancelável exigiria uma implementação explícita que verifica o valor de retorno de dispatchEvent() antes de realizar a ação solicitada. valor de retorno do dispatchEvent.
Para esta tarefa, o contrato completo é pequeno: um manipulador de botão interno atualiza o estado, um evento personalizado nomeado sai do host, e os consumidores leem sua carga útil documentada. Mantenha o ouvinte direto funcionando primeiro, depois ative as rotas de propagação que sua interface contenedora realmente precisa.
Sources
- Padrão HTML — Elementos personalizados — WHATWG.
- Padrão DOM — WHATWG.
- CustomEvent: construtor CustomEvent() — MDN Web Docs.
- EventTarget: método dispatchEvent() — MDN Web Docs.
- Event: propriedade composed — MDN Web Docs.
- Usando elementos personalizados — MDN Web Docs.
- EventTarget: método addEventListener() — MDN Web Docs.
- EventTarget: método removeEventListener() — MDN Web Docs.
- Trabalhar com documentos HTML no TextEdit no Mac — Apple.