Cours JavaScript et TypeScript · Module 8: Événements et DOM
Bonnes pratiques pour la gestion des événements
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,resizeouinput, avec le throttling ou le debouncing - Ajoute l'option
{ passive: true }aux écouteurs du tactile et de la molette qui n'appellent paspreventDefault() - 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. Pourquoi est-il utile de vérifier qu'un élément existe avant d'appeler
addEventListener?2. Pourquoi appelle-t-on
e.preventDefault()dans le gestionnaire de l'événementsubmitd'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é :