5. Les effets

2. Nettoyer les effets

Mr. Propre serait fier de nous

L'utilisation d'effets est souvent risquée : elle est souvent source de bugs.

C'est le cas pour notre application ! Essayer de changer de page, et observez la console... Le console.log d'apparition des Pokémons continue d'apparaître toutes les 2 secondes ! Pire, faites des allers/retours entre la page d'accueil et n'importe quelle autre page... l'affichage des log devient chaotique !... T_T

Rechargez la page pour revenir à la normale.

Nous avons sans nous en rendre compte créé une fuite de mémoire. C'est-à dire du code qui peut cumuler des exécutions de fonction à l'infini.

Fuite de mémoire

Plus concrètement, notre problème dans ce cas particulier est le setInterval. On démarre un intervalle de 2 secondes uniquement lorsque started de la page d'accueil passe à true... mais on ne l'arrête jamais. Un intervalle n'est pas intrinsèquement lié à un composant, c'est du code qui s'exécute "en dehors". Il ne va donc pas s'arrêter tout seul au démontage du composant.

Ce type de problème n'est pas limité au setInterval. Beaucoup d'autres situations peuvent aboutir à ce problème.

De plus, lorsque l'on revient sur la page d'accueil, on réinstancie le composant de page. Et comme les effets se jouent également lors de l'instanciation, si started vaut true, l'effet de lancement d'intervalle est rejoué, lançant alors un deuxième intervalle similaire, mais indépendant. Et en continuant à répéter l'opération, on cumule de plus en plus d'intervalles.

Dans notre exemple, le problème n'est pas trop grave, mais imaginez que l'intervalle envoie des requêtes au réseau.

Nettoyage

Ce type de problème se résout en "nettoyant" les effets. C'est-à-dire en arrêtant les processus qui ne sont plus pertinents lorsque le composant disparaît, ou juste avant que l'effet soit rejoué.

La rune $effect permet de nettoyer les effets en utilisant un callback de retour. Si vous renvoyez une fonction dans votre effet, cette fonction sera exécutée :

  • juste avant que l'effet en question soit rejoué
  • juste avant que le composant soit démonté
<script>
	$effect(() => {
		doSomething();

		return undoSomething; // `undoSomething` est une fonction annulant ce qui a été fait dans `doSomething`
	});
</script>

On peut imaginer un cas d'abonnement à une liste de messages (comme sur Slack par exemple).

<script>
	let channelId = 'general';
	$effect(() => {
		subscribeTo(channelId);
		return () => unsubscribeTo(channelId); // callback utilisant une fonction fléchée
	});
</script>

Dans le cas où l'on souhaite uniquement nettoyer quelque chose au démontage, vous pouvez faire la même chose en utilisant onMount. Vous pouvez également utiliser onDestroy importé depuis svelte.

<script>
	import { onMount } from 'svelte';

	onMount(() => {
		return cleanSomething;
	});
</script>

est similaire à

<script>
	import { onDestroy } from 'svelte';

	onDestroy(cleanSomething);
</script>

Les effets ne sont pas vos amis

De manière générale, il est plutôt rare d'utiliser des effets. Ils ne servent que pour des cas marginaux où la réactivité normale atteint ses limites. Les cas classiques d'utilisation des effets sont en général :

  • manipulation du temps
  • requêtes réseau
  • manipulation directe du DOM

En dehors de ces quelques cas, vous n'avez probablement pas besoin d'effets.

À vous !

Dans la page d'accueil

  • nettoyer l'effet déclenchant un setInterval en utilisant clearInterval.

Plus de détails sur ce chapitre ici et