Cours JavaScript et TypeScript · Module 8: Événements et DOM

Bonnes pratiques pour la gestion des événements

5 min de lecture

Les principes clés d'une gestion des événements efficace et sûre dans les applications JavaScript.

« La plupart des pannes au centre de contrôle ne viennent pas de problèmes difficiles », dit Ray Arnold en parcourant le journal des incidents. « Ce sont des broutilles : un capteur branché sur un appareil qui n'existe pas, une alarme que personne n'a éteinte, un verrou posé au cas où. » Cette leçon rassemble ces broutilles en règles qu'il vaut la peine d'avoir dans le sang. Tu les as déjà toutes vues à l'œuvre, maintenant on les range dans une seule checklist.

Les règles d'un bon code

La première règle protège contre l'erreur la plus fréquente des débutants. Avant d'attacher un écouteur, assure-toi que l'élément existe bel et bien.

1// 1. Vérifie toujours que l'élément existe
2const button = document.getElementById('my-button');
3if (button) {
4  button.addEventListener('click', handleClick);
5} else {
6  console.warn('Le bouton avec l’ID "my-button" est introuvable');
7}

La méthode getElementById() renvoie null quand la page ne contient aucun élément avec l'ID indiqué, par exemple à cause d'une faute de frappe ou d'un script lancé avant le chargement du HTML. Appeler addEventListener() sur null lève une TypeError et arrête le reste du script. Le raccourci serait l'écriture button?.addEventListener(...), mais je préfère une condition explicite avec un avertissement, car le discret ?. cache la faute de frappe au lieu de la signaler.

La deuxième règle concerne les éléments qui apparaissent et disparaissent. Au lieu d'attacher un écouteur à chaque bouton, écoute sur leur parent commun.

1// 2. Utilise la délégation pour les éléments dynamiques
2const parent = document.getElementById('dynamic-content');
3parent.addEventListener('click', (e) => {
4  if (e.target.matches('.dynamic-button')) {
5    handleDynamicButtonClick(e);
6  }
7});

La condition e.target.matches('.dynamic-button') ne laisse passer que les clics sur les boutons qui ont cette classe, donc les nouveaux boutons fonctionnent sans aucun écouteur supplémentaire. Souviens-toi de la leçon sur la délégation : matches() ne vérifie que l'élément cliqué. Si une icône se trouve dans le bouton, il est plus sûr d'utiliser closest().

La troisième règle, c'est le nettoyage. Un composant qui attache un écouteur doit aussi savoir le retirer.

1// 3. Pense à nettoyer les écouteurs d'événements
2class ParkComponent {
3  constructor(element) {
4    this.element = element;
5    this.handleClick = this.handleClick.bind(this);
6    this.init();
7  }
8
9  init() {
10    this.element.addEventListener('click', this.handleClick);
11  }
12
13  handleClick(e) {
14    console.log('Composant du parc cliqué');
15  }
16
17  destroy() {
18    this.element.removeEventListener('click', this.handleClick);
19  }
20}

La ligne clé est celle avec bind(this) dans le constructeur. La méthode bind() crée une nouvelle fonction dont le this est fixé une fois pour toutes, et on l'enregistre sous this.handleClick. Grâce à cela, init() et destroy() utilisent la même référence, donc la suppression fonctionne. Sans bind(), à l'intérieur de handleClick(), this désignerait l'élément, et non le composant.

La quatrième règle concerne le blocage des comportements par défaut du navigateur.

1// 4. Utilise preventDefault() en connaissance de cause
2const form = document.getElementById('contact-form');
3form.addEventListener('submit', (e) => {
4  e.preventDefault(); // Arrête l'envoi standard du formulaire
5
6  // Logique de validation et d'envoi personnalisée
7  if (validateForm()) {
8    submitFormAjax();
9  }
10});

Ici, preventDefault() a une raison précise : il arrête l'envoi standard et le rechargement de la page, car c'est notre propre code qui va gérer le formulaire. Ne l'appelle pas au cas où. Sur un lien, il bloquera la navigation, avec le tactile, il peut arrêter le défilement, et dans un écouteur passif, il sera tout simplement ignoré.

La cinquième règle : enregistre les écouteurs dans un seul style. La propriété onclick ne stocke qu'une seule fonction, alors que addEventListener() ajoute des écouteurs supplémentaires.

1// onclick ne stocke qu'une seule fonction - la deuxième écrase la première
2alarmButton.onclick = () => console.log('Alarme dans le secteur A');
3alarmButton.onclick = () => console.log('Alarme dans le secteur B');
4
5// addEventListener ajoute des écouteurs supplémentaires
6alarmButton.addEventListener('click', () => console.log('Prévenir la sécurité'));
7alarmButton.addEventListener('click', () => console.log('Noter dans le journal'));

Qu'affichera un seul clic ? Trois lignes : « Alarme dans le secteur B », « Prévenir la sécurité » et « Noter dans le journal ». Le message sur le secteur A a disparu sans laisser de trace, car la deuxième affectation l'a écrasé. C'est pourquoi, dans tout le projet, reste fidèle à addEventListener(), qui ne retire rien à personne.

Une courte checklist pour finir

En plus des cinq règles du code, il vaut la peine de retenir quelques autres points :

  • Garde les écouteurs que tu prévois de supprimer dans une fonction nommée ou une variable, car une fonction sans référence ne peut pas être retirée avec removeEventListener()
  • Limite les événements fréquents, comme scroll, resize ou input, avec le throttling ou le debouncing
  • Ajoute l'option { passive: true } aux écouteurs du tactile et de la molette qui n'appellent pas preventDefault()
  • Pour les éléments cliquables, utilise <button>, et non <div>

Le dernier point est facile à négliger. Un bouton reçoit gratuitement du navigateur la prise en charge du clavier : quand on appuie sur Entrée ou sur la barre d'espace, il envoie le même événement click que la souris. Un div cliquable demanderait d'écrire cette prise en charge à la main, et en général, quelqu'un l'oublie.

Mon conseil : avant de rendre ton code, passe cette liste en revue comme lors de l'inspection matinale des clôtures. Ça prend une minute et ça économise des heures de chasse aux bugs. Dans le bac à sable ci-dessous, tu verras côte à côte les bonnes et les mauvaises versions des mêmes solutions. Dans la prochaine leçon, nous aborderons les techniques avancées, en commençant par les gestes tactiles.

Retiens bien : dans le parc, il n'y a pas de petites négligences, et chaque null non géré peut ouvrir un portail que personne ne surveille.

Code de cette leçon : index.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <title>Bonnes pratiques de gestion des événements</title>
6    <style>
7        body { font-family: Arial, sans-serif; background: linear-gradient(135deg, #2c3e50, #34495e); color: white; padding: 20px; }
8        .demo-box { background: rgba(0,0,0,0.7); padding: 20px; border-radius: 10px; margin: 20px 0; }
9        .good { border-left: 4px solid #27ae60; }
10        .bad { border-left: 4px solid #e74c3c; }
11        button { padding: 10px 20px; background: #3498db; color: white; border: none; border-radius: 6px; cursor: pointer; margin: 5px; }
12        code { background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 3px; }
13    </style>
14</head>
15<body>
16    <h1>Bonnes pratiques de gestion des événements</h1>
17
18    <div class="demo-box good">
19        <h3>Bon : vérifier l'existence de l'élément</h3>
20        <button id="good-btn">Clique-moi</button>
21        <p>Code uses safe pattern <code>element?.addEventListener</code></p>
22    </div>
23
24    <div class="demo-box good">
25        <h3>Bon : délégation d'événements</h3>
26        <div id="dino-container">
27            <button class="dino-btn" data-dino="trex">T-Rex</button>
28            <button class="dino-btn" data-dino="velociraptor">Velociraptor</button>
29        </div>
30    </div>
31
32    <div class="demo-box good">
33        <h3>Bon : nettoyer les écouteurs</h3>
34        <button id="cleanup-test">Tester le nettoyage</button>
35        <p id="cleanup-status">Statut : Inactif</p>
36    </div>
37
38    <script>
39        console.log('Démonstration des bonnes pratiques');
40
41        // BONNE PRATIQUE 1 : Vérification de l'existence
42        const goodBtn = document.getElementById('good-btn');
43        if (goodBtn) {
44            goodBtn.addEventListener('click', () => {
45                console.log('Element exists - safe click');
46                alert('Safe click!');
47            });
48        }
49
50        // BONNE PRATIQUE 2 : Event Delegation
51        const dinoContainer = document.getElementById('dino-container');
52        dinoContainer.addEventListener('click', (e) => {
53            if (e.target.classList.contains('dino-btn')) {
54                const dinoName = e.target.dataset.dino;
55                console.log(`Delegated click: ${dinoName}`);
56                alert(`You selected: ${dinoName}`);
57            }
58        });
59
60        // BONNE PRATIQUE 3 : Proper Cleanup
61        class ComponentWithCleanup {
62            constructor(element) {
63                this.element = element;
64                this.handleClick = this.handleClick.bind(this);
65                this.init();
66            }
67
68            init() {
69                this.element.addEventListener('click', this.handleClick);
70                console.log('Component initialized');
71            }
72
73            handleClick() {
74                console.log('Component clicked');
75                document.getElementById('cleanup-status').textContent = 'Status: Active';
76            }
77
78            destroy() {
79                this.element.removeEventListener('click', this.handleClick);
80                console.log('Component cleaned up properly');
81                document.getElementById('cleanup-status').textContent = 'Status: Nettoyé';
82            }
83        }
84
85        const cleanupBtn = document.getElementById('cleanup-test');
86        const component = new ComponentWithCleanup(cleanupBtn);
87
88        // Simulation cleanup après 5 secondes
89        setTimeout(() => {
90            component.destroy();
91        }, 5000);
92
93        // GOOD PRACTICE 4: Use preventDefault consciously
94        document.addEventListener('keydown', (e) => {
95            if (e.ctrlKey && e.key === 's') {
96                e.preventDefault();
97                console.log('Prevented default save, implemented custom logic');
98                alert('État du parc enregistré !');
99            }
100        });
101
102        console.log('Toutes les bonnes pratiques implémentées');
103    </script>
104</body>
105</html>

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Pourquoi est-il utile de vérifier qu'un élément existe avant d'appeler addEventListener ?

  2. 2. Pourquoi appelle-t-on e.preventDefault() dans le gestionnaire de l'événement submit d'un formulaire ?

Tâches pratiques dans le jeu

  • Clique dans l'ordre

    Ordonne la condition de délégation d'événement qui vérifie l'élément cliqué :

Articles utiles