Event delegation e múltiplos seletores com vanilla JS
Tags: javascript, programming, portugues
Published: 2023-12-03 21:56:01

_**Nota:** apenas traduzi o texto abaixo e postei aqui._

Hoje, veremos abordagens para usar event delegation com múltiplos seletores.

## Como isso funciona

Se você não está familiarizado com a abordagem, veja como funciona. Em vez de attaching events a elementos específicos, você "listen" esse event em um parent element (geralmente o `document` ou `window`).

Qualquer event desse tipo que aconteça dentro do parent element aparece e você pode filtrar aqueles que não match ao element que você está procurando.

```javascript
document.addEventListener('click', function (event) {
    // Verifica a classe .click-me
    // Se o elemento clicado não possui ela, não faça nada
    if (!event.target.matches('.click-me')) return;

    // O código que você quer executar vai aqui
});
```

Se você estiver "listen" events em mais de um element com o mesmo seletor, essa abordagem é realmente melhor para desempenho porque usa menos memória no navegador.

## Múltiplos seletores

Quando compartilho essa abordagem, muitas vezes os alunos me perguntam como implementá-la quando você precisa "listen" o mesmo tipo de event para vários seletores diferentes e fazer coisas diferentes com base no seletor que o elemento possui.

Por exemplo, digamos que você tenha botões mostrar/ocultar conteúdo que possuem uma classe `.show-me` e outro conjunto de botões com a classe .save que salva o conteúdo de um form para reutilização posterior.

Como você analisa quais ações serão executadas com base no seletor?

### Opção 1: separe event listeners

A abordagem mais óbvia é usar dois event listeners separados, um para cada seletor.

```javascript
document.addEventListener('click', function (event) {
    if (!event.target.matches('.show-me')) return;
    // Revele o conteúdo escondido...
});

document.addEventListener('click', function (event) {
    if (!event.target.matches('.save')) return;
    // Salve o conteúdo do form...
});
```

Essa abordagem funciona bem se seus scripts forem mantidos em arquivos modulares separados.

Embora você esteja usando dois event listeners em vez de um, isso ainda é muito melhor para o desempenho do que "attaching" um listener a cada botão correspondente.

### Opção 2: if...else

Se os dois trechos de código estiverem no mesmo arquivo, você poderá combiná-los em um único `event listener` e usar uma instrução `if...else if`.

```javascript
document.addEventListener('click', function (event) {
    if (event.target.matches('.show-me')) {
        // Revele o conteúdo escondido...
    } else if (event.target.matches('.save')) {
        // Salve o conteúdo do form...
    }
});
```

Isso proporciona um pequeno aumento de desempenho.

## Opção 3: if e return

Muitas pessoas acham as instruções `if...else if` difíceis de manejar, especialmente se você tiver mais do que apenas algumas verificações. Eles podem crescer rapidamente e sair fora de controle.

Por esse motivo, prefiro usar instruções if sequenciais, com "returns" entre chaves para encerrar a function quando ocorrer um match.

```javascript
document.addEventListener('click', function (event) {
    if (event.target.matches('.show-me')) {
        // Revele o conteúdo escondido...
        return;
    }
    if (event.target.matches('.save')) {
        // Salve o conteúdo do form...
        return;
    }
});
```

Essa abordagem é, para mim, mais fácil de ler. Isso também significa que posso movimentar minhas verificações sem me preocupar em quebrar nada.

## Opção 4: handler functions

Se você gosta de programação funcional, essa abordagem fornece uma estrutura interessante.

Dentro do `event listener callback`, você chama `handler functions` separadas para cada seletor e passa o event object. Em cada `handler function`, você executa a verificação do seletor e executa o código de acordo.

```javascript
var showMe = function (event) {
    if (!event.target.matches('.show-me')) return;
    // Revele o conteúdo escondido...
};

var saveMe = function (event) {
    if (!event.target.matches('.save')) return;
    // Salve o conteúdo do form...
};

document.addEventListener('click', function (event) {
    showMe(event);
    saveMe(event);
});
```

Essa abordagem fornece um bom equilíbrio entre desempenho e capacidade de manutenção.

O desempenho é um pouco menor do que as opções 2 e 3, porque a function `saveMe()` será executada mesmo se `showMe()` encontrar um match (as outras duas abordagens param assim que um match é encontrado). Mas ainda é melhor do que ter event listeners separados e fornece uma estrutura de código limpa e legível.

## Qual abordagem você deve usar?

Em última análise, depende do seu projeto.

Eu uso muito a opção 1, porque costumo manter meus scripts em arquivos modulares. Mas quando o código está todo em um arquivo, a opção 4 é minha favorita.

## Sugestão (atualizado):

Através da execução de `Array.prototype.some` da lista das handler functions, você pode executar somente até a primeira que retorne `true`, desde de que todas elas retornem isso (ou `undefined` no caso negativo) durante as verificações. 

```javascript
var showMe = function (event) {
    if (!event.target.matches('.show-me')) return;
    // Revele o conteúdo escondido...
    return true;
};

var saveMe = function (event) {
    if (!event.target.matches('.save')) return;
    // Salve o conteúdo do form...
    return true;
};

var otherLogic1 = function (event) {
    // a verificação negativa
    // a lógica
    return true;
}

var otherLogic2 = function (event) {
    // a verificação negativa
    // a lógica
    return true;
}

document.addEventListener('click', function (event) {
    [showMe, saveMe, otherLogic1, otherLogic2].some(
        handler => Boolean(handler(event))
    );
});
```

`Array.prototype.some` interrompe sua iteração interna quando a primeira execução da sua `callback function` retornar `true`.


## Fonte

Newsletter de [Go Make Things](https://gomakethings.com/)