Agenda
- Was ist Svelte?
- Die Anatomie einer Svelte-Komponente
- Komposition von Komponenten in Svelte
- Ausblick auf Svelte 5
Was ist Svelte?
Svelte
/ˈsvɛlt/
schlank
=
Svelte
/ˈsvɛlt/
Svelte
/ˈsvɛlt/
schlank
=
anmutig
“Svelte is a way of writing user interface components [...] that users see and interact with in their browsers.”
Svelte ist ein Framework
Svelte ist ein Framework
für die Erstellung von deklarativen und wiederverwendbaren Komponenten, die den Zustand und das Verhalten von UI-Elementen umfassen
Logo
Register
Home
E-Mail
Submit
Logo
Register
Home
E-Mail
Submit
<Header>
<Navbar>
<Form>
<App>
Logo
Register
Home
<Header>
<Navbar>
<Form>
<App>
<NavItem>
<NavItem>
<Logo>
E-Mail
Submit
<Label>
<Input>
<Button>
Svelte ist ein Compiler
<h1>Simple Component</h1>
Component.svelte
<h1>Simple Component</h1>
<h1>Simple Component</h1>
<h1>Simple Component</h1>
import { SvelteComponent, detach, element, init,
insert, noop, safe_not_equal } from "svelte/internal";
function create_fragment(ctx) {
let h1;
return {
c() {
h1 = element("h1");
h1.textContent = "Simple Component";
},
m(target, anchor) {
insert(target, h1, anchor);
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(h1);
},
};
}
class SimpleComponent extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, create_fragment, safe_not_equal, {});
}
}
export default SimpleComponent;
<h1>Simple Component</h1>
import { [...] } from "svelte/internal";
function create_fragment(ctx) {
let h1;
return {
c() {
h1 = element("h1");
h1.textContent = "Simple Component";
},
m(target, anchor) {
insert(target, h1, anchor);
},
p: noop, i: noop, o: noop,
d(detaching) { if (detaching) detach(h1); },
};
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, create_fragment,
safe_not_equal, {});
}
}
export default Component;
Component.js
<h1>Simple Component</h1>
import Component from "./Component.js";
const component = new Component({
target: document.body,
});
export default component;
main.js
Was bietet Svelte an?
- Nah an der Webplattform – HTML, CSS und JS sind schon die halbe Miete
- Vollständig ausgestattet – Lösungen für Zustandsmanagement, Transitionen und mehr
- Großartige Leistung out-of-the-box – keine Abstraktionen wie ein virtuelles DOM erforderlich
- Kleinstmögliche Bundle-Größe – es gibt keine Laufzeitbibliothek im üblichen Sinne
- Ein ausgeklügeltes Reaktivitätsmodell – (fast) alles in Svelte ist reaktiv
Was bietet Svelte an?
Die Anatomie einer Svelte-Komponente
<h1>Simple Component</h1>
Component.svelte
<h1>Simple Component</h1>
<script>
let text = "Simple Component";
</script>
<h1>{text}</h1>
Component.svelte
<h1>Simple Component</h1>
<script>
let text = "Simple Component";
</script>
<h1>{text}</h1>
<script>
let text = "Simple Component";
</script>
<h1>{text}</h1>
<style>
h1 { color: blue; }
</style>
Component.svelte
<script>
// State & Behavior
</script>
<!-- Declarative Markup -->
<style>
/* Scoped Component Styles */
</style>
ComponentStructure.svelte
State & Reaktivität
<script>
let counter = 0;
</script>
<output>{counter}</output>
<button>Decrease</button>
<button>Increase</button>
Counter.svelte
<script>
let counter = 0;
</script>
<output>{counter}</output>
<button>Decrease</button>
<button>Increase</button>
<script>
let counter = 0;
const inc = () => counter++;
const dec = () => counter--;
</script>
<output>{counter}</output>
<button on:click={dec}>Decrease</button>
<button on:click={inc}>Increase</button>
Counter.svelte
<script>
let counter = 0;
</script>
<output>{counter}</output>
<button>Decrease</button>
<button>Increase</button>
<script>
let counter = 0;
const inc = () => counter++;
const dec = () => counter--;
</script>
<output>{counter}</output>
<button on:click={dec}>Decrease</button>
<button on:click={inc}>Increase</button>
<script>
let counter = 0;
$: doubled = counter * 2;
$: quadrupled = doubled * 2;
const inc = () => counter++;
const dec = () => counter--;
</script>
<b>x1</b>: {counter}<br>
<b>x2</b>: <output>{counter} * 2 = {doubled}</output><br>
<b>x4</b>: <output>{doubled} * 2 = {quadrupled}</output><br>
<button on:click={dec}>Decrease</button>
<button on:click={inc}>Increase</button>
Counter.svelte
Logikblöcke
<script>
let notes = [];
</script>
<form>
<input placeholder="New note text..." />
<button>Add</button>
<ul>
<li>Note 1 <button>✗</button></li>
<li>Note 2 <button>✗</button></li>
<li>Note 3 <button>✗</button></li>
</ul>
</form>
Notes.svelte
<script>
let notes = [];
</script>
<form>
<input placeholder="New note text..." />
<button>Add</button>
{#if notes.length > 0}
<ul>
{#each notes as note}
<li>
{note}
<button>✗</button>
</li>
{/each}
</ul>
{:else}
<p>No notes yet.</p>
{/if}
</form>
<script>
let notes = [];
</script>
<form>
<input placeholder="New note text..." />
<button>Add</button>
<ul>
<li>Note 1 <button>✗</button></li>
<li>Note 2 <button>✗</button></li>
<li>Note 3 <button>✗</button></li>
</ul>
</form>
Notes.svelte
<script>
let notes = []
let newNote = "";
function addNote() {
notes = [...notes, newNote];
newNote = "";
}
function removeNote(note) {
notes = notes.filter((n) => n != note);
}
</script>
<form>
<input placeholder="New note text..." bind:value={newNote} />
<button on:click={addNote}>Add</button>
{#if notes.length > 0}
<ul>
{#each notes as note}
<li>
{note}
<button on:click={() => removeNote(note)}>✗</button>
</li>
{/each}
</ul>
{:else}
<p>No notes yet.</p>
{/if}
</form>
<script>
let notes = [];
</script>
<form>
<input placeholder="New note text..." />
<button>Add</button>
{#if notes.length > 0}
<ul>
{#each notes as note}
<li>
{note}
<button>✗</button>
</li>
{/each}
</ul>
{:else}
<p>No notes yet.</p>
{/if}
</form>
<script>
let notes = [];
</script>
<form>
<input placeholder="New note text..." />
<button>Add</button>
<ul>
<li>Note 1 <button>✗</button></li>
<li>Note 2 <button>✗</button></li>
<li>Note 3 <button>✗</button></li>
</ul>
</form>
Notes.svelte
Kurze Zusammenfassung
Lokaler State
State-Variablen halten den Komponentenzustand und sind reaktiv. Diese Reaktivität wird durch Zuweisung, nicht duch Mutation ausgelöst.
Event Listeners
Event Listener werden über die Direktive on:eventname angehängt und reagieren auf DOM- und benutzerdefinierte Ereignisse.
Reactive Statements
Reactive Statements werden über das $:-Label definiert und reagieren auf Veränderungen des lokalen States.
Logik-Blöcke
Logik-Blöcke steuern den Programmfluss innerhalb des Svelte-Markups. Sie kommen in verschiedenen Varianten vor, wie z.B. {#if} oder {#each}.
Komposition von Komponenten in Svelte
App Shell
Login
Username
Password
Ausgeloggter Zustand
Hello, Karl! You're logged in!
Logout
Eingeloggter Zustand
App Shell
Login
Username
Password
Ausgeloggter Zustand
Hello, Karl! You're logged in!
Logout
Eingeloggter Zustand
App Shell
Login
Username
Password
Ausgeloggter Zustand
Hello, Karl! You're logged in!
Logout
Eingeloggter Zustand
Login
Username
Password
Ausgeloggter Zustand
Hello, Karl! You're logged in!
Logout
Eingeloggter Zustand
App Shell
User Prop
Login Event
Logout Event
<script>
import Dashboard from "./Dashboard.svelte";
import Login from "./Login.svelte";
let user = null;
function extractUser(e) {
user = e.detail.user;
}
function logoutUser() {
user = null;
}
</script>
<div>
{#if user}
<Dashboard user={user} on:logout={logoutUser} />
{:else}
<Login on:login={extractUser} />
{/if}
</div>
App.svelte
<script>
import Dashboard from "./Dashboard.svelte";
import Login from "./Login.svelte";
let user = null;
function extractUser(e) {
user = e.detail.user;
}
function logoutUser() {
user = null;
}
</script>
<div>
{#if user}
<Dashboard {user} on:logout={logoutUser} />
{:else}
<Login on:login={extractUser} />
{/if}
</div>
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
function login(e) {
const formData = new FormData(e.target);
dispatch("login", {
user: {
name: formData.get("username"),
},
});
}
</script>
<form on:submit|preventDefault={login}>
<label for="username">Username:</label>
<input type="username" id="username" name="username" />
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
<button type="submit">Login</button>
</form>
Login.svelte
<script>
import {createEventDispatcher} from 'svelte';
const dispatch = createEventDispatcher()
export let user;
function logout() {
dispatch("logout");
}
</script>
<h1>Hello, {user.name}! You're logged in!</h1>
<button on:click={logout}>Logout</button>
<script>
import Dashboard from "./Dashboard.svelte";
import Login from "./Login.svelte";
let user = null;
function extractUser(e) {
user = e.detail.user;
}
function logoutUser() {
user = null;
}
</script>
<div>
{#if user}
<Dashboard {user} on:logout={logoutUser} />
{:else}
<Login on:login={extractUser} />
{/if}
</div>
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
function login(e) {
const formData = new FormData(e.target);
dispatch("login", {
user: {
name: formData.get("username"),
},
});
}
</script>
<form on:submit|preventDefault={login}>
<label for="username">Username:</label>
<input type="username" id="username" name="username" />
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
<button type="submit">Login</button>
</form>
Dashboard.svelte
<script>
import Dashboard from "./Dashboard.svelte";
import Login from "./Login.svelte";
let user = null;
function extractUser(e) {
user = e.detail.user;
}
function logoutUser() {
user = null;
}
</script>
<div>
{#if user}
<Dashboard {user} on:logout={logoutUser} />
{:else}
<Login on:login={extractUser} />
{/if}
</div>
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
function login(e) {
const formData = new FormData(e.target);
dispatch("login", {
user: {
name: formData.get("username"),
},
});
}
</script>
<form on:submit|preventDefault={login}>
<label for="username">Username:</label>
<input type="username" id="username" name="username" />
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
<button type="submit">Login</button>
</form>
<script>
import {createEventDispatcher} from 'svelte';
const dispatch = createEventDispatcher()
export let user;
function logout() {
dispatch("logout");
}
</script>
<h1>Hello, {user.name}! You're logged in!</h1>
<button on:click={logout}>Logout</button>
App.svelte
Component Slots
Möchten Sie diese Operation wirklich ausführen? Eingegebene Daten gehen unwiderruflich verloren.
Ja
Nein
Bestätigungsdialog
Dialogtext
Möchten Sie diese Operation wirklich ausführen? Eingegebene Daten gehen unwiderruflich verloren.
Ja
Nein
<script>
import { afterUpdate, createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let open = false;
let dialog;
afterUpdate(() => (open ? dialog?.showModal() : dialog?.close()));
function close(e) {
open = false;
dispatch('close', { answer: e.target.returnValue });
dialog.returnValue = '';
}
</script>
<dialog bind:this={dialog} on:close={close}>
<form method="dialog">
<div class="body">
<slot />
</div>
<div class="footer">
<button type="submit" class="no" value="no">
<slot name="noButtonContent">No</slot>
</button>
<button type="submit" class="yes" value="yes">
<slot name="yesButtonContent">Yes</slot>
</button>
</div>
</form>
</dialog>
ConfirmationDialog.svelte
<script>
import { afterUpdate, createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let open = false;
let dialog;
afterUpdate(() => (open ? dialog?.showModal() : dialog?.close()));
function close(e) {
open = false;
dispatch('close', { answer: e.target.returnValue });
dialog.returnValue = '';
}
</script>
<dialog bind:this={dialog} on:close={close}>
<form method="dialog">
<div class="body">
<slot />
</div>
<div class="footer">
<button type="submit" class="no" value="no">
<slot name="noButtonContent">No</slot>
</button>
<button type="submit" class="yes" value="yes">
<slot name="yesButtonContent">Yes</slot>
</button>
</div>
</form>
</dialog>
<script>
import ConfirmationDialog from './ConfirmationDialog.svelte';
let dialogOpen, dialogAnswer, dialog2Answer;
let dialog2Open = (dialogOpen = false);
</script>
<button on:click={() => (dialogOpen = !dialogOpen)}>Toggle Dialog 1</button>
<output>Answer: {dialogAnswer}</output><br />
<button on:click={() => (dialog2Open = !dialog2Open)}>Toggle Dialog 2</button>
<output>Answer: {dialog2Answer}</output>
<ConfirmationDialog
bind:open={dialogOpen}
on:close={({ detail }) => (dialogAnswer = detail.answer)}
>
A dialog text.
</ConfirmationDialog>
<ConfirmationDialog
bind:open={dialog2Open}
on:close={({ detail }) => (dialog2Answer = detail.answer)}
>
Another dialog text.
<svelte:fragment slot="noButtonContent">Definitely not</svelte:fragment>
<svelte:fragment slot="yesButtonContent">Absolutly yes</svelte:fragment>
</ConfirmationDialog>
App.svelte
Svelte Stores
“A store is simply an object with a subscribe
method that allows interested parties to be notified whenever the store value changes.”
<script>
import { onDestroy } from "svelte";
import { writable } from "svelte/store";
let counterValue = 0;
let counter = writable(counterValue);
const unsubscribe = counter.subscribe((value) => (counterValue = value));
onDestroy(unsubscribe);
</script>
<output>{counterValue}</output>
<button on:click={() => counter.update((v) => v - 1)}>decrement</button>
<button on:click={() => counter.update((v) => v + 1)}>increment</button>
StoreCounter.svelte
<script>
import { writable } from "svelte/store";
let counter = writable(0);
</script>
<output>{$counter}</output>
<button on:click={() => $counter--}>decrement</button>
<button on:click={() => $counter++}>increment</button>
StoreCounter.svelte
Kurze Zusammenfassung
Komponenten-Props
Props werden verwendet, um Daten von einer Eltern- an eine Kindkomponente zu übergeben. Sie können jeden gültigen JavaScript-Datentyp halten.
Komponenten-Events
Events werden verwendet, um Daten von einer Kind- an eine Eltern-Komponente zu übergeben. Sie werden über die Direktive on:name verwendet.
Komponenten-Slots
Slots werden verwendet, um Inhalte von einer Eltern- an eine Kind-Komponente zu übergeben. Sie werden mit dem <slot>-Tag deklariert.
Stores
Stores sind Objekte, die einen Wert halten und abonniert werden können. Sie sind überall in der App verwendbar und erfahren besondere Unterstützung in Svelte.
Ausblick auf Svelte 5
“Svelte 5 is a ground-up rewrite of the framework, designed to make your apps faster, smaller, and more robust.”
Runes
“Runes are function-like symbols that provide instructions to the Svelte compiler. [...] they're part of the language.”
<script>
let counter = $state(0);
let doubled = $derived(counter * 2);
let quadrupled = $derived(doubled * 2);
const inc = () => counter++;
const dec = () => counter--;
</script>
<b>x1</b>: {counter}<br>
<b>x2</b>: <output>{counter} * 2 = {doubled}</output><br>
<b>x4</b>: <output>{doubled} * 2 = {quadrupled}</output><br>
<button onclick={dec}>Decrease</button>
<button onclick={inc}>Increase</button>
RunesCounter.svelte
Runes-Unterstützung
*.svelte
*.svelte.js
*.svelte.ts
Keine Runes-Unterstützung
*.js
*.ts
etc.
Event Handlers
“Event handlers have been given a facelift [...], in Svelte 5 they are properties like any other”
<script>
import Dashboard from './Dashboard.svelte';
import Login from './Login.svelte';
let user = $state(null);
function extractUser(loginData) {
user = loginData.user;
}
function logoutUser() {
user = null;
}
</script>
<div>
{#if user}
<Dashboard {user} onlogout={logoutUser} />
{:else}
<Login onlogin={extractUser} />
{/if}
</div>
App.svelte
<script>
import Dashboard from './Dashboard.svelte';
import Login from './Login.svelte';
let user = $state(null);
function extractUser(loginData) {
user = loginData.user;
}
function logoutUser() {
user = null;
}
</script>
<div>
{#if user}
<Dashboard {user} onlogout={logoutUser} />
{:else}
<Login onlogin={extractUser} />
{/if}
</div>
<script>
let { onlogin } = $props();
function login(e) {
e.preventDefault();
const formData = new FormData(e.target);
onlogin({
user: {
name: formData.get('username')
}
});
}
</script>
<form onsubmit={login}>
<label for="username">Username:</label>
<input type="username" id="username" name="username" />
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
<button type="submit">Login</button>
</form>
Login.svelte
<script>
let { user, onlogout } = $props();
</script>
<h1>Hello, {user.name}! You're logged in!</h1>
<button onclick={onlogout}>Logout</button>
<script>
import Dashboard from "./Dashboard.svelte";
import Login from "./Login.svelte";
let user = null;
function extractUser(e) {
user = e.detail.user;
}
function logoutUser() {
user = null;
}
</script>
<div>
{#if user}
<Dashboard {user} on:logout={logoutUser} />
{:else}
<Login on:login={extractUser} />
{/if}
</div>
<script>
let { onlogin } = $props();
function login(e) {
e.preventDefault();
const formData = new FormData(e.target);
onlogin({
user: {
name: formData.get('username')
}
});
}
</script>
<form onsubmit={login}>
<label for="username">Username:</label>
<input type="username" id="username" name="username" />
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
<button type="submit">Login</button>
</form>
Dashboard.svelte
Snippets and Render Tags
“Snippets, and render tags, are a way to create reusable chunks of markup inside your components.”
<script>
import ConfirmationDialog from './ConfirmationDialog.svelte';
let dialogOpen = $state(false);
let dialogAnswer = $state();
let dialog2Open = $state(false);
let dialog2Answer = $state();
</script>
<button onclick={() => (dialogOpen = !dialogOpen)}>Toggle Dialog 1</button>
<output>Answer: {dialogAnswer}</output><br />
<button onclick={() => (dialog2Open = !dialog2Open)}>Toggle Dialog 2</button>
<output>Answer: {dialog2Answer}</output>
<ConfirmationDialog bind:open={dialogOpen} onclose={({ answer }) => (dialogAnswer = answer)}>
A dialog text.
</ConfirmationDialog>
{#snippet yesButtonContent()}Absolutely yes{/snippet}
<ConfirmationDialog
bind:open={dialog2Open}
onclose={({ answer }) => (dialog2Answer = answer)}
{yesButtonContent}
>
Another dialog text.
{#snippet noButtonContent()}Definitely not{/snippet}
</ConfirmationDialog>
App.svelte
<script>
import ConfirmationDialog from './ConfirmationDialog.svelte';
let dialogOpen = $state(false);
let dialogAnswer = $state();
let dialog2Open = $state(false);
let dialog2Answer = $state();
</script>
<button onclick={() => (dialogOpen = !dialogOpen)}>Toggle Dialog 1</button>
<output>Answer: {dialogAnswer}</output><br />
<button onclick={() => (dialog2Open = !dialog2Open)}>Toggle Dialog 2</button>
<output>Answer: {dialog2Answer}</output>
<ConfirmationDialog bind:open={dialogOpen} onclose={({ answer }) => (dialogAnswer = answer)}>
A dialog text.
</ConfirmationDialog>
{#snippet yesButtonContent()}Absolutely yes{/snippet}
<ConfirmationDialog
bind:open={dialog2Open}
onclose={({ answer }) => (dialog2Answer = answer)}
{yesButtonContent}
>
Another dialog text.
{#snippet noButtonContent()}Definitely not{/snippet}
</ConfirmationDialog>
<script>
let { noButtonContent, yesButtonContent, children, onclose, open } = $props();
let dialog = $state();
$effect(() => open ? dialog.showModal(): dialog.close())
function close(e) {
open = false;
onclose({ answer: e.target.returnValue });
dialog.returnValue = '';
}
</script>
<dialog bind:this={dialog} onclose={close}>
<form method="dialog">
<div class="body">
{@render children()}
</div>
<div class="footer">
<button type="submit" class="no" value="no">
{#if noButtonContent}{@render noButtonContent()}{:else}No{/if}
</button>
<button type="submit" class="yes" value="yes">
{#if yesButtonContent}{@render yesButtonContent()}{:else}Yes{/if}
</button>
</div>
</form>
</dialog>
ConfirmationDialog.svelte
LinkedIn:
Xing:
Twitter:
Vielen Dank!
Die Reaktive Offenbarung: Eine Einführung in Svelte
By Nils Röhrig
Die Reaktive Offenbarung: Eine Einführung in Svelte
- 825