Skip to main content

$effect

Les effets sont des fonctions qui sont exécutées lors que l’état se met à jour, et peuvent être utilisé pour faire des choses comme appeler les librairies tierces, dessiner sur des éléments <canvas>, ou faire des appels réseau. Ils ne sont joués que dans le navigateur, jamais pendant le rendu côté serveur.

De manière générale, vous ne devriez pas mettre à jour d’état au sein d’un effet, car cela rendra votre code plus imbriqué et augmentera le risque de boucle infinie. Si vous êtes amené•e à faire cela, voir la section quand ne pas utiliser d’effet pour découvrir des approches alternatives.

Vous pouvez créer un effet avec la rune $effect (demo)

<script>
	let size = $state(50);
	let color = $state('#ff3e00');

	let canvas;

	$effect(() => {
		const context = canvas.getContext('2d');
		context.clearRect(0, 0, canvas.width, canvas.height);

		// ceci va être rejoué lorsque `color` ou `size` change
		context.fillStyle = color;
		context.fillRect(0, 0, size, size);
	});
</script>

<canvas bind:this={canvas} width="100" height="100" />

Lorsque Svelte exécute une fonction d’effet, il suit les morceaux d’état (ou d’état dérivé) qui sont lus (à moins qu’ils ne soient lus avec untrack), et ré-exécute la fonction lorsque cet état évolue plus tard.

Si vous avez des difficultés à comprendre pourquoi votre $effect est ré-exécuté ou non, voir la section comprendre les dépendances. Les effets sont déclenchés différemment des blocs $: auxquels vous êtes habitué•es si vous venez de Svelte 4.

Comprendre le cycle de vie

Vos effets sont joués après le montage du composant dans le DOM, et dans une micro-tâche après la mise à jour d’un état. Les ré-exécutions sont “batchées” (c-à-d que changer color et size au même moment ne va pas déclencher deux ré-exécutions distinctes), et se produisent après que toutes les modifications du DOM ont été appliquées.

Vous pouvez utiliser $effect partout, juste pas à la racine d’un composant, tant que $effect est appelée lors de l’exécution d’un effet parent.

Svelte utilise les effets en interne pour représenter de la logique et des expressions dans vos templates — c’est comme ça que <h1>bonjour {name} !</h1> se met à jour lorsque name change.

Un effet peut renvoyer une fonction de nettoyage qui sera jouée immédiatement avant la ré-exécution de l’effet (demo).

<script>
	let count = $state(0);
	let milliseconds = $state(1000);

	$effect(() => {
		// ceci sera recréé lorsque `milliseconds` change
		const interval = setInterval(() => {
			count += 1;
		}, milliseconds);

		return () => {
			// si une fonction de nettoyage est fourni, il sera rejoué
			// a) immédiatement avant que l'effet ne se rejoue
			// b) lorsque que le composant est démonté
			clearInterval(interval);
		};
	});
</script>

<h1>{count}</h1>

<button onclick={() => (milliseconds *= 2)}>ralentir</button>
<button onclick={() => (milliseconds /= 2)}>accélérer</button>

Les fonctions de nettoyage sont aussi nettoyées lorsque l’effet est détruit, ce qui se produit lorsque le parent est détruit (par exemple, si le composant est démonté), ou si un effet parent est ré-exécuté.

Comprendre les dépendances

$effect détecte automatiquement toute valeur réactive ($state, $derived, $props) qui est lue de manière synchrone à l’intérieur de son corps de fonction (en les incluant indirectement, via l’appel de la fonction) et l’enregistre en tant que dépendance. Lorsque ces dépendances changent, l’$effect va planifier une ré-exécution.

Si $state et $derived sont utilisées directement dans un $effect (par exemple, durant la création d’une classe réactive), ces valeurs ne seront pas traitées comme dépendances.

Les valeurs qui sont lues de manière asynchrones — après un await ou à l’intérieur d’un setTimeout, par exemple — ne seront pas considérées comme dépendances. Ici, le canvas sera repeint lorsque color change, mais pas lorsque size change (démo) :

function $effect(fn: () => void | (() => void)): void
namespace $effect

Runs code when a component is mounted to the DOM, and then whenever its dependencies change, i.e. $state or $derived values. The timing of the execution is after the DOM has been updated.

Example:

$effect(() => console.log('The count is now ' + count));

If you return a function from the effect, it will be called right before the effect is run again, or when the component is unmounted.

Does not run during server side rendering.

https://svelte.dev/docs/svelte/$effect

@paramfn The function to execute
$effect
(() => {
const const context: CanvasRenderingContext2Dcontext =
let canvas: {
    width: number;
    height: number;
    getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
}
canvas
.function getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2DgetContext('2d');
const context: CanvasRenderingContext2Dcontext.CanvasRect.clearRect(x: number, y: number, w: number, h: number): voidclearRect(0, 0,
let canvas: {
    width: number;
    height: number;
    getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
}
canvas
.width: numberwidth,
let canvas: {
    width: number;
    height: number;
    getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
}
canvas
.height: numberheight);
// cet effet sera rejoué lorsque `color` change... const context: CanvasRenderingContext2Dcontext.CanvasFillStrokeStyles.fillStyle: string | CanvasGradient | CanvasPatternfillStyle = let color: stringcolor; function setTimeout<[]>(callback: () => void, ms?: number): NodeJS.Timeout (+2 overloads)

Schedules execution of a one-time callback after delay milliseconds.

The callback will likely not be invoked in precisely delay milliseconds. Node.js makes no guarantees about the exact timing of when callbacks will fire, nor of their ordering. The callback will be called as close as possible to the time specified.

When delay is larger than 2147483647 or less than 1, the delay will be set to 1. Non-integer delays are truncated to an integer.

If callback is not a function, a TypeError will be thrown.

This method has a custom variant for promises that is available using timersPromises.setTimeout().

@sincev0.0.1
@paramcallback The function to call when the timer elapses.
@paramdelay The number of milliseconds to wait before calling the callback.
@paramargs Optional arguments to pass when the callback is called.
@returnfor use with {@link clearTimeout}
setTimeout
(() => {
// ... mais pas lorsque `size` change const context: CanvasRenderingContext2Dcontext.CanvasRect.fillRect(x: number, y: number, w: number, h: number): voidfillRect(0, 0, let size: numbersize, let size: numbersize); }, 0); });

Un effet est seulement rejoué lorsque l’objet qu’il lit change, pas lorsqu’une propriété de cet objet change. (If vous souhaitez observer les changement à l’intérieur d’un objet lors de vos développements, vous pouvez utiliser $inspect.)

<script>
	let state = $state({ value: 0 });
	let derived = $derived({ value: state.value * 2 });

	// ceci sera joué une seule fois, car `state` n'est jamais réassigné (seulement muté)
	$effect(() => {
		state;
	});

	// ceci sera rejoué à chaque fois que `state.value` change...
	$effect(() => {
		state.value;
	});

	// ... et ceci également, car `derived` est un nouvel objet à chaque fois
	$effect(() => {
		derived;
	});
</script>

<button onclick={() => (state.value += 1)}>
	{state.value}
</button>

<p>Le double de {state.value} vaut {derived.value}</p>

Un effet dépend uniquement des valeurs qu’il a lues la dernière fois qu’il a été joué. Ceci a des conséquences intéressantes pour les effets qui impliquent du code conditionnel.

Par exemple, si a vaut true dans le code ci-dessous, le code dans le bloc #if sera joué et b sera ré-évalué. Dans ce cas, n’importe quel changement sur a ou b va déclencher la ré-exécution de l’effet.

À l’inverse, si a vaut false, b ne sera pas ré-évalué, et l’effet ne sera rejoué que si a change.

function $effect(fn: () => void | (() => void)): void
namespace $effect

Runs code when a component is mounted to the DOM, and then whenever its dependencies change, i.e. $state or $derived values. The timing of the execution is after the DOM has been updated.

Example:

$effect(() => console.log('The count is now ' + count));

If you return a function from the effect, it will be called right before the effect is run again, or when the component is unmounted.

Does not run during server side rendering.

https://svelte.dev/docs/svelte/$effect

@paramfn The function to execute
$effect
(() => {
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without calling require('console').

Warning: The global console object’s methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
//   Error: Whoops, something bad happened
//     at [eval]:5:15
//     at Script.runInThisContext (node:vm:132:18)
//     at Object.runInThisContext (node:vm:309:38)
//     at node:internal/process/execution:77:19
//     at [eval]-wrapper:6:22
//     at evalScript (node:internal/process/execution:76:60)
//     at node:internal/main/eval_string:23:3

const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);

myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err

const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
@seesource
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100
log
('effet');
if (let a: falsea) { var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without calling require('console').

Warning: The global console object’s methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
//   Error: Whoops, something bad happened
//     at [eval]:5:15
//     at Script.runInThisContext (node:vm:132:18)
//     at Object.runInThisContext (node:vm:309:38)
//     at node:internal/process/execution:77:19
//     at [eval]-wrapper:6:22
//     at evalScript (node:internal/process/execution:76:60)
//     at node:internal/main/eval_string:23:3

const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);

myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err

const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
@seesource
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100
log
('b:', let b: falseb);
} });

$effect.pre

Dans de rares cas, vous pourriez avoir besoin d’exécuter votre code avant que le DOM ne soit mis à jour. Pour cela, nous avons accès à la rune $effect.pre :

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

	let div = $state();
	let messages = $state([]);

	// ...

	$effect.pre(() => {
		if (!div) return; // pas encore monté

		// référence à la longueur du tableau `messages` afin que ce code soit rejoué lorsque celle-ci
		// change
		messages.length;

		// scroll automatique lorsque de nouveaux messages sont ajoutés
		if (div.offsetHeight + div.scrollTop > div.scrollHeight - 20) {
			tick().then(() => {
				div.scrollTo(0, div.scrollHeight);
			});
		}
	});
</script>

<div bind:this={div}>
	{#each messages as message}
		<p>{message}</p>
	{/each}
</div>

Le timing mis à part, $effect.pre fonctionne exactement comme $effect.

$effect.tracking

La rune $effect.tracking est une fonctionnalité avancée qui vous informe de si le code est exécuté au sein d’un contexte de suivi (tracking), comme un effet ou bien dans le template (démo) :

<script>
	console.log('lors de la mise en place du composant :', $effect.tracking()); // false

	$effect(() => {
		console.log('dans l\'effet :', $effect.tracking()); // true
	});
</script>

<p>dans le template: {$effect.tracking()}</p> <!-- true -->

C’est notamment utilisé pour implémenter des abstractions comme createSubscriber, qui servent à créer des gestionnaires permettant de mettre à jour des valeurs réactives seulement si ces valeurs sont trackées (plutôt que, par exemple, simplement lues dans un gestionnaire d’évènement).

$effect.root

La rune $effect.root est une fonctionnalité avancée qui crée un scope non suivi qui ne s’auto-nettoie pas. Cela est utile pour les effets imbriqués que vous souhaitez contrôler manuellement. Cette rune permet également la création d’effets en dehors de la phase d’initialisation du composant.

<script>
	let count = $state(0);

	const cleanup = $effect.root(() => {
		$effect(() => {
			console.log(count);
		});

		return () => {
			console.log('nettoyage de l\'effet racine');
		};
	});
</script>

Quand ne pas utiliser $effect

En général, $effect est plutôt considéré comme un dernier recours — pratique pour des choses comme les analytics ou la manipulation directe du DOM — plutôt qu’un outil que vous devriez utiliser souvent. En particulier, évitez de vous en servir pour synchroniser un état. Plutôt que faire ceci...

<script>
	let count = $state(0);
	let doubled = $state();

	// ne faites pas ça !
	$effect(() => {
		doubled = count * 2;
	});
</script>

... faites ceci :

<script>
	let count = $state(0);
	let doubled = $derived(count * 2);
</script>

Pour des choses plus compliquées qu’une simple expression comme count * 2, vous pouvez aussi utiliser $derived.by.

Si vous utilisez un effet parce que vous souhaitez réassigner une valeur dérivée (pour construire une interface optimiste par exemple), notez que les valeurs dérivées peuvent être réassignées directement depuis Svelte 5.25.

Vous pourriez être tenté•e de faire des choses tordues avec les effets pour lier une valeur à une autre. L’exemple suivant montre deux inputs pour “argent dépensé” et “argent restant” qui sont connectés l’un à l’autre. Si vous en mettez un à jour, l’autre s’ajuste automatiquement. N’utilisez pas d’effets pour faire ça (démo.

<script>
	let total = 100;
	let spent = $state(0);
	let left = $state(total);

	$effect(() => {
		left = total - spent;
	});

	$effect(() => {
		spent = total - left;
	});
</script>

<label>
	<input type="range" bind:value={spent} max={total} />
	{spent}/{total} dépensé
</label>

<label>
	<input type="range" bind:value={left} max={total} />
	{left}/{total} restant
</label>

Utilisez plutôt des callbacks oninput ou — encore mieux — des liaisons de fonctions lorsque c’est possible (démo).

<script>
	let total = 100;
	let spent = $state(0);
	let left = $state(total);

	function updateSpent(value) {
		spent = value;
		left = total - spent;
	}

	function updateLeft(value) {
		left = value;
		spent = total - left;
	}
</script>

<label>
	<input type="range" bind:value={() => spent, updateSpent} max={total} />
	{spent}/{total} dépensé
</label>

<label>
	<input type="range" bind:value={() => left, updateLeft} max={total} />
	{left}/{total} restant
</label>

Si vous devez absolument mettre à jour un $state dans un effet et que cela déclenche une boucle infinie parce que vous lisez et écrivez un même $state, utilisez untrack.

Modifier cette page sur Github

précédent suivant