Eventos customizados
LidoNesta aula, você aprenderá a criar e disparar eventos customizados em JavaScript usando a API CustomEvent e o método dispatchEvent. Exploraremos como essa técnica permite a comunicação eficiente entre componentes, com exemplos práticos e boas práticas.
Em aplicações JavaScript modernas, especialmente aquelas construídas com componentes, a comunicação entre diferentes partes do sistema é essencial. Embora possamos usar callbacks, promessas ou gerenciamento de estado, os eventos customizados oferecem uma abordagem desacoplada e flexível. Eles permitem que um componente emita um evento que outros componentes podem ouvir, sem que haja uma referência direta entre eles. Nesta aula, vamos explorar a API de eventos customizados do JavaScript: como criar eventos personalizados com CustomEvent, como dispará-los com dispatchEvent e como usá-los para comunicação entre componentes.
O modelo de eventos do JavaScript já é amplamente usado para eventos nativos como click, keydown e load. Eventos customizados estendem esse modelo, permitindo que você defina seus próprios tipos de evento com dados associados. Isso é particularmente útil em arquiteturas baseadas em componentes, onde a comunicação direta entre componentes pode criar acoplamento desnecessário.
CustomEvent
A interface CustomEvent é uma extensão da interface Event que permite criar eventos com dados personalizados. Ela é usada para criar um objeto de evento que pode ser disparado em qualquer elemento DOM. O construtor CustomEvent aceita dois argumentos: o tipo do evento (uma string) e um objeto de opções opcional.
O objeto de opções pode conter as propriedades padrão de Event (como bubbles, cancelable, composed) e também uma propriedade detail que carrega os dados personalizados que você deseja transmitir. Esses dados podem ser qualquer valor JavaScript: um objeto, uma string, um número, etc.
Vejamos um exemplo simples de criação de um evento customizado:
// Cria um evento customizado chamado 'userLogin' com dados de usuário
const loginEvent = new CustomEvent('userLogin', {
detail: {
username: 'joaosilva',
timestamp: Date.now()
},
bubbles: true,
cancelable: true
});
No exemplo acima, criamos um evento do tipo userLogin com um objeto detail contendo informações do usuário. A opção bubbles define se o evento irá propagar pela árvore DOM, e cancelable indica se o evento pode ser cancelado (útil se você quiser permitir que listeners cancelem a ação padrão).
Uma característica importante é que o evento customizado pode ser disparado em qualquer elemento DOM, e ele irá percorrer o DOM de acordo com as opções de propagação. Isso significa que você pode ter listeners em elementos ancestrais que capturam o evento.
dispatchEvent
O método dispatchEvent é usado para disparar um evento em um elemento DOM. Ele está disponível em todos os elementos DOM (via Element.prototype) e também no window, document e outros objetos que implementam a interface EventTarget. Quando você chama dispatchEvent, o evento é entregue ao alvo e, se bubbles for verdadeiro, ele sobe pela árvore DOM até o topo.
Para disparar o evento criado no exemplo anterior, fazemos:
// Dispara o evento no elemento 'loginButton'
const loginButton = document.getElementById('loginButton');
loginButton.dispatchEvent(loginEvent);
Normalmente, você dispara um evento em resposta a uma ação do usuário ou a uma mudança de estado. Por exemplo, quando um usuário faz login, você pode disparar um evento userLogin no elemento raiz da aplicação ou em um elemento específico para notificar outros componentes.
É importante notar que dispatchEvent é síncrono. Ou seja, os listeners são chamados imediatamente durante a chamada de dispatchEvent. Isso pode ser vantajoso para garantir que a reação ao evento ocorra antes de continuar a execução.
Outra utilidade é que você pode disparar eventos em qualquer elemento, não necessariamente no elemento que originou a ação. Por exemplo, você pode disparar um evento no document para que todos os componentes possam ouvir.
// Disparando um evento global no document
document.dispatchEvent(new CustomEvent('app:init'));
Isso é comum em aplicações onde você quer que qualquer parte do sistema reaja a uma inicialização global.
Comunicação entre componentes
Eventos customizados são uma excelente ferramenta para comunicação entre componentes, especialmente em arquiteturas como Web Components ou em frameworks que não impõem um padrão de comunicação (como React, mas com eventos). O princípio é que um componente emite um evento e outros componentes podem escutá-lo, sem precisar conhecer a origem diretamente.
Vamos imaginar um cenário em que temos um componente de cabeçalho com um botão de busca e um componente de lista de resultados. Quando o usuário digita uma busca, o cabeçalho deve notificar a lista para atualizar os resultados. Em vez de passar referências ou usar um estado global, podemos usar um evento customizado.
Primeiro, definimos o evento no cabeçalho:
// Componente de cabeçalho
const searchInput = document.getElementById('search-input');
const searchButton = document.getElementById('search-button');
searchButton.addEventListener('click', () => {
const query = searchInput.value;
// Cria e dispara um evento de busca
const searchEvent = new CustomEvent('search:query', {
detail: { query },
bubbles: true
});
searchButton.dispatchEvent(searchEvent);
});
No componente de lista, escutamos o evento:
// Componente de lista de resultados
document.addEventListener('search:query', (event) => {
const { query } = event.detail;
// Atualiza a lista com os resultados da busca
fetchResults(query).then(renderResults);
});
Perceba que o componente de lista não precisa saber de onde vem o evento; ele apenas escuta no document. Isso é uma forma de comunicação desacoplada.
Outra abordagem é usar um elemento central como um barramento de eventos (event bus). Você pode criar um objeto que serve como um hub para eventos:
// Event bus simples
const EventBus = {
emit(eventName, detail) {
const event = new CustomEvent(eventName, { detail });
document.dispatchEvent(event);
},
on(eventName, callback) {
document.addEventListener(eventName, callback);
}
};
// Uso
EventBus.emit('user:login', { user: 'joao' });
EventBus.on('user:login', (e) => {
console.log('Usuário logado:', e.detail.user);
});
Essa abordagem permite que qualquer componente envie e receba eventos sem criar dependências diretas.
É importante lembrar que eventos customizados são síncronos e podem ser cancelados. Se você precisar de comunicação assíncrona ou de um padrão mais robusto, considere usar outras técnicas, como Promises ou gerenciamento de estado.
Boas práticas
Ao usar eventos customizados, siga algumas boas práticas para manter o código limpo e previsível:
- Use nomes de eventos descritivos e consistentes, como
user:loginoucart:add. Evite nomes genéricos comoevent1. - Documente os eventos que seus componentes emitem, incluindo a estrutura de
detail, para que outros desenvolvedores saibam como usá-los. - Evite abusar de eventos customizados para comunicação excessiva. Se dois componentes estão sempre se comunicando, talvez seja melhor ter uma relação mais direta.
- Considere usar
bubbles: truepara que eventos possam ser capturados em níveis mais altos, mas lembre-se de que isso pode causar escuta acidental em outros componentes. - Para eventos globais, prefira dispará-los no
documentou em um elemento raiz, e use nomes com prefixo (comoapp:) para evitar colisões.
Referências
- MDN - CustomEvent
- MDN - dispatchEvent
- MDN - Creating and triggering events
- MDN - Event
- W3C - CustomEvent Specification
Exercícios
-
Crie um evento customizado chamado
cart:addque carrega emdetailum objeto comproductIdequantity. Dispare esse evento em um botão de adicionar ao carrinho.✓ Resposta:// Supondo que exista um botão com id 'add-to-cart' const addButton = document.getElementById('add-to-cart'); addButton.addEventListener('click', () => { const event = new CustomEvent('cart:add', { detail: { productId: 123, quantity: 1 }, bubbles: true }); addButton.dispatchEvent(event); }); -
Escreva um listener para o evento do exercício anterior que exiba no console a mensagem "Produto 123 adicionado ao carrinho (quantidade: 1)". Use
document.addEventListener.✓ Resposta:document.addEventListener('cart:add', (event) => { const { productId, quantity } = event.detail; console.log(`Produto ${productId} adicionado ao carrinho (quantidade: ${quantity})`); }); -
Implemente um pequeno sistema de eventos onde um componente 'header' dispara um evento 'search:submit' com o termo de busca, e outro componente 'results' escuta esse evento e atualiza um elemento
<div>com o termo recebido. Use um event bus simples.✓ Resposta:// Event bus const EventBus = { emit(eventName, detail) { document.dispatchEvent(new CustomEvent(eventName, { detail })); }, on(eventName, callback) { document.addEventListener(eventName, callback); } }; // Header component const searchInput = document.getElementById('search-input'); const searchButton = document.getElementById('search-button'); searchButton.addEventListener('click', () => { EventBus.emit('search:submit', { term: searchInput.value }); }); // Results component const resultsDiv = document.getElementById('results'); EventBus.on('search:submit', (event) => { resultsDiv.textContent = `Resultados para: ${event.detail.term}`; }); -
Explique a diferença entre
bubbles: trueebubbles: falseem um evento customizado. Dê um exemplo de quando você usaria cada um.✓ Resposta:Quando
bubblesétrue, o evento propaga do elemento alvo para cima na árvore DOM, permitindo que listeners em elementos ancestrais (comodocument) o capturem. Isso é útil para comunicação global ou para capturar eventos em um nível mais alto. Quandobubbleséfalse, o evento só é entregue ao elemento alvo; listeners em ancestrais não serão acionados. Isso é útil para eventos que devem ser tratados apenas pelo próprio elemento, evitando efeitos colaterais em outros componentes.Exemplo: um evento
input:changedisparado em um campo de formulário pode terbubbles: falsese apenas o componente do formulário precisa reagir. Já um eventouser:logoutpode terbubbles: truepara que toda a aplicação possa reagir. -
Crie um evento customizado que seja cancelável e escreva um listener que chame
event.preventDefault()para cancelar a ação padrão. Demonstre como verificar se o evento foi cancelado após o dispatch.✓ Resposta:// Cria um evento cancelável const cancelableEvent = new CustomEvent('cancelable-action', { cancelable: true }); // Listener que cancela o evento document.addEventListener('cancelable-action', (event) => { event.preventDefault(); console.log('Evento cancelado!'); }); // Dispara o evento e verifica se foi cancelado const wasCancelled = !document.dispatchEvent(cancelableEvent); console.log('Foi cancelado?', wasCancelled); // trueO método
dispatchEventretornafalsese o evento foi cancelado por algum listener (viapreventDefault()), etruecaso contrário.
Observações finais
Eventos customizados são uma ferramenta poderosa para criar aplicações modulares e desacopladas. Eles permitem que diferentes partes do sistema se comuniquem de forma limpa, sem criar dependências diretas. Aprender a usar CustomEvent e dispatchEvent é essencial para desenvolvedores que trabalham com componentes, seja em Web Components, frameworks ou JavaScript puro.
Lembre-se de sempre documentar os eventos e seus dados, e de usar nomes significativos. Com a prática, você verá como essa técnica pode simplificar a arquitetura do seu código.