Skip to main content

$state

La rune $state vous permet de créer un état réactif, ce qui signifie que votre UI réagit lorsque cet état évolue.

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

<button onclick={() => count++}>
	clics: {count}
</button>

À la différence d’autres frameworks que vous avez peut-être croisés, il n’y a pas d’API pour interagir avec un état — count est juste un nombre, et non un objet ou une fonction, et vous pouvez le mettre à jour comme vous mettriez à jour n’importe quelle variable.

L’état profond

Si $state est utilisé avec un tableau ou un objet simple, le résultat est un proxy d’état profondément réactif. Les Proxys permettent à Svelte d’exécuter du code lors de la lecture ou de l’écriture de propriétés, en particulier via des méthodes comme array.push(...), déclenchant des mises à jour granulaires.

L’état est transformé en Proxy de manière récursive jusqu’à ce que Svelte trouve autre chose qu’un tableau ou un objet simple (comme une classe par exemple, ou un objet créé avec Objet.create). Dans un cas comme celui-là...

let todos = $state([
	{
		done: false,
		text: 'ajouter plus de todos'
	}
]);

... modifier une propriété d’un élément individuel du tableau va déclencher des mises à jour pour tous les éléments de votre UI qui dépendent spécifiquement de cette propriété :

todos[0].done = !todos[0].done;

Si vous ajoutez un nouvel objet à ce tableau via .push, celui-ci sera également transformé en Proxy :

todos.push({
	done: false,
	text: 'déjeuner'
});

Lorsque vous mettez à jour les propriétés d’un Proxy, l’objet d’origine n’est pas muté. Si vous avez besoin d’utiliser vos propres gestionnaires de proxy dans un proxy d’état, vous devriez entourer l’objet après l’avoir entouré d’un $state.

Notez que si vous déstructurez une valeur réactive, les références ne sont pas réactives — comme pour du JavaScript classique, elles sont évaluées au moment de la déstructuration :

let { done, text } = todos[0];

// ceci n'affectera pas la valeur de `done`
todos[0].done = !todos[0].done;

Classes

Les classes ne sont pas transformées en proxy. À la place, vous pouvez utiliser $state avec les champs d’une classe (qu’ils soient publics ou privés), ou comme première assignation à une propriété à la racine du constructor :

class Todo {
	done = $state(false);

	constructor(text) {
		this.text = $state(text);
	}

	reset() {
		this.text = '';
		this.done = false;
	}
}

Le compilateur transforme done et text en méthodes get / set sur le prototype de classe qui référencent des champs privés. Cela signifie que les propriétés ne sont pas énumérables.

Lorsque vous appelez des méthodes en JavaScript, la valeur de this a de l’importance. L’exemple suivant ne fonctionne pas, car la référence this dans la méthode reset est le <button> plutôt que le Todo :

<button onclick={todo.reset}>
	réinitialiser
</button>

Vous pouvez soit utiliser une fonction inlinée...

<button onclick={() => todo.reset()}>
	réinitialiser
</button>

... soit une fonction fléchée dans la définition de la classe :

class Todo {
	done = $state(false);

	constructor(text) {
		this.text = $state(text);
	}

	reset = () => {
		this.text = '';
		this.done = false;
	}
}

Classes intégrées

Svelte fournit des implémentations réactives de classes intégrées comme Set, Map, Date et URL que vous pouvez importer depuis svelte/reactivity.

$state.raw

Si vous ne souhaitez pas que les objets et tableaux soient profondément réactifs, vous pouvez utiliser $state.raw.

Un état déclaré avec $state.raw ne peut pas être muté ; il peut seulement être réassigné. En d’autres mots, plutôt que de réassigner une propriété ou un objet, ou d’utiliser une méthode de tableau comme push, remplacez entièrement l’objet ou le tableau si vous voulez le mettre à jour :

let person = $state.raw({
	name: 'Heraclitus',
	age: 49
});

// ceci n'aura aucun effet
person.age += 1;

// ceci fonctionne, car nous créons une nouvelle personne
person = {
	name: 'Heraclitus',
	age: 50
};

Ceci peut améliorer la performance dans le cas de grands tableaux et objets que vous ne prévoyez de toutes façons pas de muter, puisqu’il n’y a pas le surcoût de les rendre profondément réactifs. Notez qu’un état raw peut contenir des états réactifs (par exemple, un tableau raw d’objets réactifs).

Vous pouvez déclarer des champs de classe avec $state.raw, comme vous le feriez avec $state.

$state.snapshot

Pour prendre un instantané statique d’un Proxy profondément réactif $state, utilisez $state.snapshot :

<script>
	let counter = $state({ count: 0 });

	function onclick() {
		// va afficher `{ count: ... }` plutôt que `Proxy { ... }`
		console.log($state.snapshot(counter));
	}
</script>

Ceci est pratique lorsque vous souhaitez passer un état à une librairie externe ou à une API qui ne s’attend pas à recevoir un proxy, comme dans le cas de structuredClone.

Si une valeur a une méthode toJSON, le snapshot va cloner la valeur renvoyée depuis toJSON plutôt que l’objet original.

$state.eager

Lorsque un état est mis à jour, il peut ne pas être reflété dans l’interface immédiatement s’il est utilisé dans une expression await, car les mises à jour sont synchronisées.

Dans certains cas, vous pourriez vouloir mettre à jour l’interface dès que l’état est mis à jour. Par exemple, vous pourriez vouloir mettre à jour une barre de navigation lorsque l’utilisateur ou l’utilisatrice clique sur un lien, afin d’afficher un retour visuel pendant le chargement de la nouvelle page. Pour faire cela, utilisez $state.eager(value) :

<nav>
	<a href="/" aria-current={$state.eager(pathname) === '/' ? 'page' : null}>accueil</a>
	<a href="/about" aria-current={$state.eager(pathname) === '/about' ? 'page' : null}>à propos</a>
</nav>

Utilisez cette fonctionnalité avec parcimonie, et uniquement pour fournir des retours en réponse à des actions utilisateur — en général, laisser Svelte coordonner les mises à jour va permettre une meilleure expérience utilisateur.

Passer de l’état à des fonctions

JavaScript est un langage qui passe par valeur — lorsque vous appelez une fonction, les arguments sont les valeurs plutôt que les variables. En d’autres mots :

index
/**
 * @param {number} a
 * @param {number} b
 */
function add(a, b) {
	return a + b;
}

let a = 1;
let b = 2;
let total = add(a, b);
console.log(total); // 3

a = 3;
b = 4;
console.log(total); // toujours 3!
function add(a: number, b: number) {
	return a + b;
}

let a = 1;
let b = 2;
let total = add(a, b);
console.log(total); // 3

a = 3;
b = 4;
console.log(total); // toujours 3!

Si add a besoin d’un accès aux valeurs courantes de a et b, et de renvoyer la valeur total mise à jour, vous devez utiliser plutôt des fonctions :

index
/**
 * @param {() => number} getA
 * @param {() => number} getB
 */
function add(getA, getB) {
	return () => getA() + getB();
}

let a = 1;
let b = 2;
let total = add(() => a, () => b);
console.log(total()); // 3

a = 3;
b = 4;
console.log(total()); // 7
function add(getA: () => number, getB: () => number) {
	return () => getA() + getB();
}

let a = 1;
let b = 2;
let total = add(() => a, () => b);
console.log(total()); // 3

a = 3;
b = 4;
console.log(total()); // 7

L’état dans Svelte fonctionne de la même façon — lorsque vous référencez quelque chose déclaré avec la rune $state...

let a = $state(1);
let b = $state(2);

... vous accédez à sa valeur courante.

Notez que le terme “fonctions” est large — il englobe les propriétés des proxies et les propriétés get et set...

index
/**
 * @param {{ a: number, b: number }} input
 */
function add(input) {
	return {
		get value() {
			return input.a + input.b;
		}
	};
}

let input = $state({ a: 1, b: 2 });
let total = add(input);
console.log(total.value); // 3

input.a = 3;
input.b = 4;
console.log(total.value); // 7
function add(input: { a: number, b: number }) {
	return {
		get value() {
			return input.a + input.b;
		}
	};
}

let input = $state({ a: 1, b: 2 });
let total = add(input);
console.log(total.value); // 3

input.a = 3;
input.b = 4;
console.log(total.value); // 7

... toutefois si vous vous retrouvez à écrire du code similaire, envisagez plutôt l’utilisation de classes.

Passer de l’état entre les modules

Vous pouvez déclarer un état dans des fichiers .svelte.js et .svelte.ts, mais vous pouvez uniquement exporter cet état s’il n’est pas directement réassigné. En d’autres mots, vous ne pouvez pas faire ceci :

state.svelte
export let count = $state(0);

export function increment() {
	count += 1;
}

La raison est que chaque référence à count est transformée par le compilateur Svelte — le code ci-dessus est grosso modo équivalent à ceci :

state.svelte
export let count = $.state(0);

export function increment() {
	$.set(count, $.get(count) + 1);
}

Vous pouvez voir le code que Svelte génère en cliquant sur l’onglet ‘JS Output’ dans le bac à sable.

Puisque le compilateur ne traite qu’un seule fichier à la fois, si un autre fichier importe count, Svelte ne saura pas qu’il doit entourer chaque référence de $.get $.set :

import { count } from './state.svelte.js';

console.log(typeof count); // 'object', pas 'number'

Ceci vous laisse deux options pour partager un état entre vos modules — soit ne pas le réassigner...

// Ceci est autorisé — puisque nous mettons
// à jour `counter.count` et pas `counter`,
// Svelte ne l'entoure pas de `$.state`
export const counter = $state({
	count: 0
});

export function increment() {
	counter.count += 1;
}

... ou ne pas l’exporter directement :

let count = $state(0);

export function getCount() {
	return count;
}

export function increment() {
	count += 1;
}

Modifier cette page sur Github llms.txt

précédent suivant