Svelte 5 führt neue Runen-API ein

Im November 2023 kündigte das Svelte-Team die Beta zu Svelte 5 an. Die Preview hält neue APIs bereit und ist auf npm verfügbar. Zeit für einen Überblick!

In Pocket speichern vorlesen Druckansicht 43 Kommentare lesen

(Bild: Blackboard/Shutterstock.com)

Lesezeit: 11 Min.
Von
  • Simon Holthausen
Inhaltsverzeichnis

Svelte ist ein UI-Framework, das deklarativen Code wie den folgenden in optimierten JavaScript-Code verwandelt, der das User Interface immer dann updatet, wenn count erhöht wird.

<script>
	let count = 0;

	function increment() {
		count += 1;
	}
</script>

<button on:click="{increment}">clicks: {count}</button>

Die Umwandlung funktioniert, da der Compiler "sieht", wo count referenziert wird, und weil er Syntax wie let oder = als Anzeichen dafür interpretiert, dass count ein Zustand ist, der veränderbar ist. Das Resultat: Es braucht weniger Code, da der Compiler sich um das Meiste kümmert.

Leider funktioniert diese schöne deklarative Welt nur auf der obersten Ebene von Komponenten – möchte man eine wiederverwendbare createCounter-Methode extrahieren, muss man stattdessen Svelte's Stores nutzen:

// Definition
import { writable } from 'svelte/store';

export function createCounter() {
	const { subscribe, update } = writable(0);

	return {
		subscribe,
		increment: () => update((n) => n + 1)
	};
}

Bei der Verwendung lässt sich dann zwar die schöne Kurzschreibweise für das Subscriben auf Stores nutzen (der $-Prefix), einige Entwicklerinnen und Entwickler finden diesen jedoch ungewöhnlich. Zudem lässt er sich nur innerhalb von .svelte-Dateien verwenden.

<!-- Verwendung -->
<script>
	import { createCounter } from './somewhere.js';

	const count = createCounter();
</script>

<button on:click="{count.increment}">clicks: {$count}</button>

Wäre es nicht schöner, wenn dieselbe ergonomische und elegante Syntax ohne Stores, die von Svelte-Komponenten bekannt ist, überall zum Einsatz kommen könnte?

Svelte 5 ermöglicht genau das durch die Einführung von Runes – zu Deutsch: Runen. Runen sind Buchstaben oder Zeichen, die als magische Symbole genutzt werden – und weil sich das Svelte-Entwicklerteam nicht zu ernst nimmt, wurde die API danach benannt. Nüchterner formuliert sind sie Compiler-Anweisungen. Die einfachste davon ist die $state-Rune, um einen reaktiven Zustand zu definieren:

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

	function increment() {
		count += 1;
	}
</script>

<button on:click="{increment}">clicks: {count}</button>

$state ist hier keine Funktion, sondern eine Anweisung an den Compiler, diese Variable besonders zu behandeln und nach Änderungen an ihrem Zustand das User Interface neu zu rendern. Der Wert, der hineingeben wird, ist der, der auch herauskommt – aus TypeScript-Sicht ist das also die Identitätsfunktion.

Für solch einen einfachen Fall mag das auf den ersten Blick wie ein Rückschritt wirken, da es etwas mehr Code erfordert. Auf den zweiten Blick bietet es jedoch zahlreiche Vorteile: Der Code ist lesbarer, da sich unmittelbar nachvollziehen lässt, welche Variablen einen Zustand ausdrücken, und welche nicht. Vor allem jedoch ermöglichen sie es, den Code viel einfacher zu extrahieren und zu refaktorieren, ohne, dass man auf Sveltes Stores zurückgreifen muss. Eine createCounter-Methode mit Runes sieht dann so aus:

export function createCounter() {
	let count = $state(0);

	return {
		get count() {
			return count;
		},
		increment: () => count++
	};
}

Die Getter-Methode ist nötig, damit die Reaktivität von count bewahrt bleibt und über die Funktionsgrenzen hinweg transportiert wird. So bekommt jede Stelle, die count ausliest, den aktuellen Wert zugespielt.

Wer lieber Klassen zur Beschreibung solcher Objekte nutzt, kann das ebenfalls tun. Der Code sähe dann stattdessen so aus:

class Counter {
	count = $state(0);
	increment = () => this.count++;
}

Die Verwendung sieht dann folgendermaßen aus:

<script>
	import { createCounter } from './somewhere.svelte.js';
	// oder { Counter } from ...

	const counter = createCounter(); // oder new Counter()
</script>

<button on:click="{counter.increment}">clicks: {counter.count}</button>