JavaScript — Disparando Eventos Personalizados
Aprenda a criar e disparar eventos personalizados em JavaScript com CustomEvent e dispatchEvent, passar dados pelo detail e desacoplar componentes.
Disparando Eventos Personalizados em JavaScript
O navegador dispara eventos nativos como click, submit e keydown automaticamente. Mas o JavaScript também permite que você crie e dispare seus próprios eventos em qualquer nó do DOM, e os escute com o mesmo addEventListener() que você já usa para eventos nativos.
Esta é a base do design de aplicações fracamente acopladas: uma parte do seu código anuncia que algo aconteceu ("dados carregados", "carrinho atualizado") sem saber nem se importar com quem está ouvindo. Esta página explica como criar eventos personalizados com o construtor CustomEvent, anexar dados a eles, dispará-los com dispatchEvent() e lidar com os problemas mais comuns.
Criando um evento personalizado
Use o construtor CustomEvent. O primeiro argumento é o tipo do evento (o nome que você vai escutar depois); o segundo é um object de opções:
let event = new CustomEvent("myEvent", {
detail: { message: "This is a custom event!" },
bubbles: true,
cancelable: true
});As três opções que você vai usar com mais frequência são:
detail— qualquer valor (object, string, número) que você queira anexar ao evento. O listener o lê comoevent.detail. Este é o canal dedicado para dados personalizados; as propriedades de eventos nativos são somente leitura.bubbles— quandotrue, o evento sobe pelos elementos ancestrais após ser disparado, então um listener em um elemento pai (ou emdocument) pode capturá-lo. O padrão éfalse.cancelable— quandotrue, um listener pode chamarevent.preventDefault()para indicar que a ação padrão deve ser ignorada. O padrão éfalse.
CustomEvent vs. Event
Existe também um construtor Event simples, mas ele não pode carregar dados — não tem detail. Use sempre CustomEvent quando precisar passar informações junto com o evento:
// No way to attach data here:
let bare = new Event("ping", { bubbles: true });
// Use CustomEvent to send a payload:
let withData = new CustomEvent("ping", {
bubbles: true,
detail: { at: Date.now() }
});Disparando o evento
Um evento criado não faz nada até que você o dispare em um elemento com dispatchEvent():
let target = document.getElementById("box");
target.dispatchEvent(event);Dois pontos importantes sobre dispatchEvent():
- Ele é executado de forma síncrona — ao contrário de
setTimeout, os listeners são executados imediatamente, antes que a linha apósdispatchEvent()seja executada. - Ele retorna um boolean:
falsese o evento eracancelablee um listener chamoupreventDefault(), caso contráriotrue. Isso permite que o código que disparou o evento reaja a um "veto":
let cancelled = !target.dispatchEvent(event);
if (cancelled) {
console.log("A listener prevented the default action.");
}Você pode disparar em qualquer elemento. Disparar em document funciona porque eventos com bubbling o alcançam, mas disparar no elemento específico que "possui" o evento (e deixá-lo subir) mantém seus listeners com escopo definido e evita handlers globais acidentais.
Escutando um evento personalizado
Não há uma API especial do lado do listener — addEventListener() funciona exatamente como para click. Os dados chegam em event.detail:
element.addEventListener("myEvent", function (event) {
console.log(event.detail.message); // "This is a custom event!"
});Exemplos práticos
Exemplo 1: Comunicação entre componentes
Suponha que duas partes independentes de uma página precisem se comunicar sem ter referências diretas uma para a outra. Uma dispara um evento personalizado; a outra escuta. Como o evento usa bubbles, o listener pode ficar em document:
<button id="sender">Send Message</button>// Listener in another component
document.addEventListener('componentMessage', function(event) {
alert('Received message: ' + event.detail.message);
});
document.getElementById('sender').addEventListener('click', function() {
// Create and dispatch the custom event
let customEvent = new CustomEvent('componentMessage', {
detail: { message: 'Hello from another component!' },
bubbles: true,
cancelable: true
});
document.dispatchEvent(customEvent);
});Como funciona:
- Clicar no botão cria e dispara um evento
componentMessagecarregando uma mensagem emdetail. - Um listener em outro lugar captura o evento e reage a ele. Nenhum dos lados importa o outro — o nome do evento é o único contrato.
Exemplo 2: Atualizando a interface após uma mudança de dados
Eventos personalizados permitem separar a lógica de dados da lógica de renderização. A camada de dados anuncia dataUpdated; a camada de UI escuta e re-renderiza. Aqui a atualização é acionada após um pequeno atraso para simular uma requisição assíncrona:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom Event UI Update Example</title>
</head>
<body>
<h1>User Status</h1>
<div id="userInfo">Loading user information...</div>
<script>
// Function to simulate a data update
function updateData() {
let dataUpdateEvent = new CustomEvent('dataUpdated', {
detail: { data: { username: 'user123', status: 'active' } }
});
document.dispatchEvent(dataUpdateEvent);
}
// UI component listening for data updates
document.addEventListener('dataUpdated', function(event) {
let userData = event.detail.data;
document.getElementById('userInfo').innerHTML =
`Username: <strong>${userData.username}</strong>, Status: <strong>${userData.status}</strong>`;
});
// Trigger the update after 2 seconds
setTimeout(updateData, 2000);
</script>
</body>
</html>Como funciona:
- Após dois segundos,
updateData()dispara um eventodataUpdatedcom os novos dados emdetail. - O listener de UI lê
event.detail.datae atualiza a página. A função que produziu os dados nunca toca o DOM diretamente.
Cancelando um evento personalizado
Quando um evento é cancelable, um listener pode chamar preventDefault(). O dispatcher então vê um retorno false de dispatchEvent() e pode ignorar seu comportamento padrão — o mesmo padrão que o navegador usa para envio de formulários ou cliques em links. Este trecho executa no Node e demonstra o fluxo síncrono:
let target = new EventTarget();
target.addEventListener("save", (event) => {
// Veto the save
event.preventDefault();
});
let event = new CustomEvent("save", { cancelable: true });
let notCancelled = target.dispatchEvent(event);
console.log(notCancelled); // false
console.log(event.defaultPrevented); // trueEventTarget é a interface base que os nós do DOM herdam, então a mesma lógica se aplica tanto ao disparar em um elemento no navegador quanto em um EventTarget independente.
Conclusão
Eventos personalizados oferecem uma maneira limpa e sem dependências de framework para conectar partes de uma aplicação. Crie-os com CustomEvent, anexe um payload via detail, dispare-os com dispatchEvent() e escute com o familiar addEventListener(). Defina bubbles: true quando um elemento pai precisar capturar o evento, e cancelable: true quando o dispatcher precisar respeitar o veto de um listener. O resultado é um código onde os módulos se comunicam por meio de eventos nomeados em vez de referências diretas — mais fácil de testar, estender e manter.
Para aprofundar, veja como os eventos percorrem o DOM em bubbling e capturing, como os listeners são anexados em tratamento de eventos no DOM, e como preventDefault() afeta as ações padrão do navegador.