Delegação de eventos

1. Fundamentos da Delegação de Eventos

A delegação de eventos é uma técnica fundamental no desenvolvimento JavaScript que permite gerenciar eventos de forma eficiente, aproveitando o mecanismo de propagação (event bubbling) do DOM. Em vez de atribuir listeners individuais a cada elemento, você atribui um único listener a um elemento ancestral e utiliza a lógica condicional para tratar eventos originados em seus descendentes.

O mecanismo de propagação funciona da seguinte forma: quando um evento é disparado em um elemento, ele primeiro executa os handlers no próprio elemento (fase de captura), depois sobe pela árvore DOM até o documento raiz (fase de bubbling). A delegação explora principalmente a fase de bubbling.

// Estrutura básica de propagação
document.querySelector('#container').addEventListener('click', (event) => {
  // event.target: elemento que originou o evento
  // event.currentTarget: elemento onde o listener foi registrado (#container)
  console.log('Alvo:', event.target.tagName);
  console.log('Ouvinte:', event.currentTarget.id);
});

A diferença crucial entre event.target e event.currentTarget é que o primeiro sempre se refere ao elemento que disparou o evento, enquanto o segundo permanece constante como o elemento onde o listener foi registrado.

2. Implementando Delegação no DOM com JavaScript Puro

A implementação prática da delegação segue um padrão claro: selecionar um elemento pai, adicionar um único listener e identificar o elemento alvo através de event.target ou propriedades como closest().

// Exemplo: gerenciar cliques em uma lista dinâmica
const lista = document.querySelector('#minhaLista');

lista.addEventListener('click', (event) => {
  const item = event.target.closest('li');

  if (!item) return; // Ignora cliques fora dos itens

  const acao = item.dataset.acao;

  switch(acao) {
    case 'editar':
      editarItem(item);
      break;
    case 'excluir':
      excluirItem(item);
      break;
    case 'selecionar':
      selecionarItem(item);
      break;
  }
});

// Adicionando novos itens dinamicamente - sem necessidade de novos listeners
function adicionarItem(texto, acao) {
  const li = document.createElement('li');
  li.textContent = texto;
  li.dataset.acao = acao;
  lista.appendChild(li);
}

3. Vantagens da Delegação em Cenários Reais

A delegação oferece benefícios significativos em aplicações modernas:

Redução de memória: Em uma tabela com 1000 linhas, um único listener substitui 1000 listeners individuais.

Suporte a elementos dinâmicos: Elementos adicionados após o carregamento da página são automaticamente gerenciados.

// Exemplo: tabela grande com delegação
const tabela = document.querySelector('#tabelaClientes');

tabela.addEventListener('click', (event) => {
  const linha = event.target.closest('tr');
  if (!linha || linha === tabela) return;

  const clienteId = linha.dataset.clienteId;
  carregarDetalhesCliente(clienteId);
});

// Adicionar 1000 linhas sem preocupação com listeners
for (let i = 0; i < 1000; i++) {
  adicionarLinhaTabela(`Cliente ${i}`, i);
}

4. Limitações e Cuidados com a Delegação

Nem todos os eventos propagam adequadamente para delegação. Eventos como focus, blur, scroll, load e error não fazem bubbling naturalmente.

// Eventos que NÃO propagam - requerem abordagens alternativas
elemento.addEventListener('focus', handler); // Não delega
elemento.addEventListener('scroll', handler); // Não delega

// Solução para foco: usar focusin/focusout (propagam)
pai.addEventListener('focusin', (event) => {
  // event.target recebeu foco
});

O uso de stopPropagation() pode quebrar a cadeia de delegação. Evite-o em handlers delegados ou use com extrema cautela.

Em árvores DOM muito profundas, a delegação pode ter impacto de performance devido à verificação de cada ancestral até encontrar o elemento alvo.

5. Delegação de Eventos no Node.js (Contexto Server-Side)

No ambiente Node.js, o padrão de delegação de eventos é implementado através do módulo EventEmitter, que permite criar sistemas de eventos personalizados.

const EventEmitter = require('events');

class ProcessadorDeArquivos extends EventEmitter {
  constructor() {
    super();
    this.delegarEventos();
  }

  delegarEventos() {
    // Delega eventos específicos para handlers especializados
    this.on('arquivo', (dados) => {
      if (dados.tipo === 'imagem') {
        this.emit('imagem:processar', dados);
      } else if (dados.tipo === 'texto') {
        this.emit('texto:processar', dados);
      }
    });
  }

  processarStream(stream) {
    stream.on('data', (chunk) => {
      // Delega para handlers baseados no tipo de dado
      this.emit('dado', chunk);
    });
  }
}

const processador = new ProcessadorDeArquivos();

processador.on('imagem:processar', (dados) => {
  console.log('Processando imagem:', dados.nome);
});

processador.on('texto:processar', (dados) => {
  console.log('Processando texto:', dados.nome);
});

Diferente do cliente, no servidor os eventos são puramente programáticos e não envolvem interação direta com o DOM.

6. Delegação de Eventos no React

O React gerencia eventos através de synthetic events, que são wrappers dos eventos nativos. Por padrão, o React já utiliza delegação ao anexar listeners ao nó raiz do documento.

import React, { useState } from 'react';

function ListaTarefas() {
  const [tarefas, setTarefas] = useState([
    { id: 1, texto: 'Estudar React', status: 'pendente' },
    { id: 2, texto: 'Praticar delegação', status: 'concluida' }
  ]);

  const handleClick = (event) => {
    const tarefaId = event.currentTarget.dataset.id;
    const acao = event.target.dataset.acao;

    if (acao === 'concluir') {
      setTarefas(prev => prev.map(t => 
        t.id === Number(tarefaId) ? { ...t, status: 'concluida' } : t
      ));
    } else if (acao === 'excluir') {
      setTarefas(prev => prev.filter(t => t.id !== Number(tarefaId)));
    }
  };

  return (
    <ul onClick={handleClick}>
      {tarefas.map(tarefa => (
        <li key={tarefa.id} data-id={tarefa.id}>
          <span>{tarefa.texto}</span>
          <button data-acao="concluir">✓</button>
          <button data-acao="excluir">✗</button>
        </li>
      ))}
    </ul>
  );
}

A abordagem nativa do React com synthetic events já oferece delegação automática, mas para casos específicos onde você precisa de mais controle, pode utilizar event.target e atributos data-*.

7. Padrões Avançados e Boas Práticas

Combinando delegação com throttling:

function throttle(fn, limit) {
  let inThrottle = false;
  return function(...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

container.addEventListener('scroll', throttle((event) => {
  // Processa scroll delegado com controle de frequência
  const elemento = event.target.closest('.item');
  if (elemento) {
    carregarMaisConteudo(elemento);
  }
}, 200));

Debugging de eventos delegados:

// Helper para depuração
function debugDelegacao(event) {
  console.group('Evento delegado');
  console.log('Tipo:', event.type);
  console.log('Target:', event.target);
  console.log('CurrentTarget:', event.currentTarget);
  console.log('Caminho:', event.composedPath());
  console.groupEnd();
}

container.addEventListener('click', (event) => {
  debugDelegacao(event);
  // Lógica principal aqui
});

Boas práticas para delegação:
- Use closest() em vez de matches() para navegar para cima na árvore
- Evite delegação muito genérica - seja específico com seletores
- Documente claramente a hierarquia de eventos no código
- Considere o impacto em performance para árvores DOM muito profundas

Referências