Guia do Desenvolvedor para Eventos Personalizados em JavaScript
Você construiu uma arquitetura de componentes limpa, mas agora as peças precisam se comunicar sem criar acoplamento rígido. Eventos DOM nativos lidam bem com interações do usuário, mas e quanto aos sinais específicos da sua aplicação? Eventos Personalizados em JavaScript resolvem este problema de forma elegante.
Este guia cobre a criação e o disparo de eventos personalizados, a passagem de dados estruturados através do payload detail do CustomEvent e o uso de EventTarget como um barramento de eventos leve. Você também aprenderá como eventos personalizados funcionam em Web Components e como eventos personalizados do Shadow DOM se propagam através de fronteiras.
Pontos-Chave
- Use o construtor
CustomEventcom a propriedadedetailpara passar dados estruturados através de eventos - A interface
EventTargetfunciona de forma independente, permitindo padrões pub/sub leves sem elementos DOM - Defina
bubbles: truepara eventos que devem se propagar pela árvore DOM - Use
composed: truepara permitir que eventos atravessem fronteiras do Shadow DOM em Web Components
Criando e Disparando Eventos Personalizados
O construtor CustomEvent é a forma moderna de criar eventos personalizados. Esqueça initCustomEvent—essa é uma API legada que você não precisará em navegadores modernos.
const event = new CustomEvent('user-login', {
detail: { userId: 123, timestamp: Date.now() },
bubbles: true,
cancelable: true
})
document.querySelector('#app').dispatchEvent(event)
Três opções são importantes aqui:
- detail: Seu payload estruturado (qualquer dado serializável)
- bubbles: Se o evento se propaga pela árvore DOM
- cancelable: Se os listeners podem chamar
preventDefault()
A escuta funciona exatamente como eventos nativos:
document.querySelector('#app').addEventListener('user-login', (e) => {
console.log(e.detail.userId) // 123
})
O Payload Detail do CustomEvent
A propriedade detail é onde os eventos personalizados brilham em comparação ao construtor básico Event. Embora você possa tecnicamente atribuir propriedades arbitrárias a um objeto Event após a criação, detail fornece um namespace dedicado e livre de conflitos para seus dados.
const cartEvent = new CustomEvent('cart-updated', {
detail: {
items: [{ id: 1, qty: 2 }, { id: 3, qty: 1 }],
total: 59.99,
currency: 'USD'
}
})
Os manipuladores acessam isso através de event.detail—limpo e previsível.
EventTarget do DOM como Barramento de Eventos
Você não precisa de um elemento DOM para usar eventos. A interface EventTarget funciona de forma independente, tornando-a perfeita para um mecanismo pub/sub leve:
class AppEventBus extends EventTarget {
emit(eventName, data) {
this.dispatchEvent(new CustomEvent(eventName, { detail: data }))
}
on(eventName, handler) {
this.addEventListener(eventName, handler)
}
off(eventName, handler) {
this.removeEventListener(eventName, handler)
}
}
const bus = new AppEventBus()
bus.on('notification', (e) => console.log(e.detail.message))
bus.emit('notification', { message: 'Hello!' })
Este padrão de barramento de eventos baseado em EventTarget mantém os componentes desacoplados sem dependências externas. Para usuários de TypeScript, você pode tipar o payload detail usando genéricos: CustomEvent<{ message: string }>.
Nota: No Node.js, EventTarget existe, mas CustomEvent só está disponível como global no Node 19+. O EventEmitter do Node permanece o padrão mais comum lá.
Discover how at OpenReplay.com.
Eventos Personalizados em Web Components
Web Components dependem fortemente de eventos personalizados para comunicação externa. Um componente dispara eventos enquanto o código pai escuta:
class UserCard extends HTMLElement {
connectedCallback() {
this.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('user-selected', {
detail: { id: this.dataset.userId },
bubbles: true,
composed: true
}))
})
}
}
customElements.define('user-card', UserCard)
O pai escuta sem conhecer os detalhes internos do componente:
document.querySelector('user-card').addEventListener('user-selected', (e) => {
loadUserProfile(e.detail.id)
})
Eventos Personalizados do Shadow DOM e a Opção Composed
Eventos personalizados do Shadow DOM se comportam de forma diferente com base na opção composed:
composed: false(padrão): O evento para na fronteira da shadow root. A implementação interna permanece oculta.composed: true: O evento atravessa fronteiras do shadow e propaga-se através do light DOM.
// Dentro do shadow DOM
this.shadowRoot.querySelector('button').dispatchEvent(
new CustomEvent('internal-action', {
bubbles: true,
composed: true // Escapa da fronteira do shadow
})
)
Quando um evento atravessa a fronteira do shadow, event.target é redirecionado para o elemento host—listeners externos veem o componente, não sua estrutura interna.
Use composed: true para eventos que o código externo deve manipular. Mantenha composed: false para comunicação interna do componente.
Uma nota rápida sobre event.isTrusted: esta propriedade indica se o navegador (ação do usuário) ou script gerou o evento. É informacional, não um mecanismo de segurança—não confie nela para controle de acesso.
Conclusão
Eventos Personalizados em JavaScript fornecem uma forma agnóstica de framework para construir arquiteturas orientadas a eventos e fracamente acopladas. Use CustomEvent com um payload detail estruturado para dados, aproveite EventTarget como um barramento de eventos independente e entenda como bubbles e composed controlam a propagação—especialmente através de fronteiras do Shadow DOM. Esses padrões escalam desde comunicação simples entre componentes até arquiteturas complexas de micro-frontends.
Perguntas Frequentes
A principal diferença é a propriedade detail. CustomEvent inclui uma propriedade detail dedicada para passar dados estruturados, enquanto o construtor básico Event não possui. Embora você possa adicionar propriedades a um objeto Event após a criação, usar CustomEvent com detail é mais limpo e evita potenciais conflitos de nomenclatura com propriedades de evento existentes.
Sim, mas com ressalvas. React não suporta nativamente listeners de eventos personalizados em JSX, então você precisará usar refs e anexar listeners manualmente com addEventListener. Vue lida melhor com eventos personalizados através de seu sistema de eventos. Eventos personalizados funcionam melhor ao comunicar entre componentes de framework e Web Components vanilla ou código agnóstico de framework.
Não. Defina bubbles como true apenas quando elementos pai precisarem capturar o evento. Para comunicação direta onde você dispara e escuta no mesmo elemento, bubbling é desnecessário. Bubbling excessivo pode levar a listeners não intencionais capturando eventos, então seja intencional sobre a propagação.
Use removeEventListener com exatamente a mesma referência de função que você passou para addEventListener. Funções anônimas não podem ser removidas porque não há referência para passar. Armazene sua função manipuladora em uma variável ou use o padrão AbortController com a opção signal para uma limpeza mais limpa.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.