Files
Job-Info/svelte-reference/01-RUNES_GUIDE.md
T
aewing 0b76a4b119 Cleanup: Remove old modes, add Svelte 5 reference library, update orchestrator
- Delete Mode1Test, Mode2Test, Mode3Test, Mode5Test (keep only Mode6Test)
- Delete old documentation, Docker files, and k8s configs
- Add comprehensive Svelte 5 reference library (svelte-reference/)
- Update ModeSwitch orchestrator:
  - Fix port to 3005 (never use 3000/3001)
  - Add API endpoints for mode switching
  - Add bun-types and proper tsconfig
  - Clean up mode folder mappings
2026-01-21 03:56:01 +00:00

440 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Svelte 5 Runes Complete Guide
## Overview
Runes are Svelte 5's new reactivity system. They replace the old `$:` reactive statements and `export let` declarations with explicit, predictable primitives.
---
## 📦 `$state` - Reactive State
### Basic Usage
```svelte
<script lang="ts">
let count = $state(0);
let user = $state({ name: 'John', age: 30 });
let items = $state<string[]>([]);
</script>
<button onclick={() => count++}>
Clicked {count} times
</button>
```
### Deep Reactivity
```svelte
<script lang="ts">
// Objects and arrays are deeply reactive
let todos = $state([
{ id: 1, text: 'Learn Svelte', done: false }
]);
function toggle(id: number) {
const todo = todos.find(t => t.id === id);
if (todo) todo.done = !todo.done; // This triggers updates!
}
</script>
```
### `$state.raw` - Shallow Reactivity
```svelte
<script lang="ts">
// Use for large objects where you replace, not mutate
let data = $state.raw<BigObject | null>(null);
async function load() {
data = await fetchData(); // Must reassign, not mutate
}
</script>
```
### `$state.snapshot` - Get Plain Object
```svelte
<script lang="ts">
let user = $state({ name: 'John' });
function save() {
// Get a plain, non-reactive copy
const plain = $state.snapshot(user);
localStorage.setItem('user', JSON.stringify(plain));
}
</script>
```
---
## 🔗 `$derived` - Computed Values
### Basic Derived
```svelte
<script lang="ts">
let count = $state(0);
let doubled = $derived(count * 2);
let quadrupled = $derived(doubled * 2);
</script>
<p>{count} × 2 = {doubled}</p>
<p>{doubled} × 2 = {quadrupled}</p>
```
### Complex Derived with `$derived.by`
```svelte
<script lang="ts">
let items = $state([1, 2, 3, 4, 5]);
let filter = $state('all');
// Use $derived.by for complex computations
let filtered = $derived.by(() => {
console.log('Recomputing filtered list...');
if (filter === 'even') {
return items.filter(n => n % 2 === 0);
}
if (filter === 'odd') {
return items.filter(n => n % 2 !== 0);
}
return items;
});
</script>
```
### Derived from Multiple Sources
```svelte
<script lang="ts">
let firstName = $state('John');
let lastName = $state('Doe');
let fullName = $derived(`${firstName} ${lastName}`);
let initials = $derived(`${firstName[0]}${lastName[0]}`);
</script>
```
---
## ⚡ `$effect` - Side Effects
### Basic Effect
```svelte
<script lang="ts">
let count = $state(0);
$effect(() => {
// Runs when count changes
console.log(`Count is now ${count}`);
document.title = `Count: ${count}`;
});
</script>
```
### Effect with Cleanup
```svelte
<script lang="ts">
let running = $state(false);
$effect(() => {
if (!running) return;
const interval = setInterval(() => {
console.log('tick');
}, 1000);
// Cleanup function
return () => {
clearInterval(interval);
};
});
</script>
```
### `$effect.pre` - Before DOM Update
```svelte
<script lang="ts">
let messages = $state<string[]>([]);
let container: HTMLDivElement;
$effect.pre(() => {
// Runs BEFORE DOM updates
// Useful for preserving scroll position
messages; // Track dependency
if (container) {
// Save scroll position before new messages render
}
});
</script>
```
### `$effect.tracking` - Check if Reactive
```svelte
<script lang="ts">
$effect(() => {
console.log($effect.tracking()); // true
});
function regularFunction() {
console.log($effect.tracking()); // false
}
</script>
```
### `$effect.root` - Untracked Effect Scope
```svelte
<script lang="ts">
// Create effects outside component lifecycle
const cleanup = $effect.root(() => {
$effect(() => {
// This effect lives until cleanup() is called
});
return () => {
// Custom cleanup logic
};
});
// Later: cleanup();
</script>
```
---
## 🎯 `$props` - Component Props
### Basic Props
```svelte
<script lang="ts">
let { name, age }: { name: string; age: number } = $props();
</script>
<p>{name} is {age} years old</p>
```
### Optional Props with Defaults
```svelte
<script lang="ts">
interface Props {
name: string;
age?: number;
greeting?: string;
}
let {
name,
age = 18,
greeting = 'Hello'
}: Props = $props();
</script>
<p>{greeting}, {name}! You are {age}.</p>
```
### Rest Props
```svelte
<script lang="ts">
import type { HTMLButtonAttributes } from 'svelte/elements';
interface Props extends HTMLButtonAttributes {
variant?: 'primary' | 'secondary';
}
let { variant = 'primary', ...rest }: Props = $props();
</script>
<button class={variant} {...rest}>
<slot />
</button>
```
### Children as Props (Snippets)
```svelte
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
header?: Snippet;
children: Snippet;
footer?: Snippet;
}
let { header, children, footer }: Props = $props();
</script>
<div class="card">
{#if header}
<header>{@render header()}</header>
{/if}
<main>{@render children()}</main>
{#if footer}
<footer>{@render footer()}</footer>
{/if}
</div>
```
---
## 🔄 `$bindable` - Two-Way Binding Props
### Basic Bindable
```svelte
<!-- TextInput.svelte -->
<script lang="ts">
let { value = $bindable('') }: { value?: string } = $props();
</script>
<input bind:value />
<!-- Parent.svelte -->
<script lang="ts">
import TextInput from './TextInput.svelte';
let name = $state('');
</script>
<TextInput bind:value={name} />
<p>Name: {name}</p>
```
### Bindable with Fallback
```svelte
<script lang="ts">
// Provide a fallback for when parent doesn't bind
let { checked = $bindable(false) }: { checked?: boolean } = $props();
</script>
<input type="checkbox" bind:checked />
```
---
## 🎨 `$inspect` - Development Debugging
### Basic Inspect
```svelte
<script lang="ts">
let count = $state(0);
let user = $state({ name: 'John' });
// Logs whenever count or user changes
$inspect(count, user);
</script>
```
### Custom Inspect Handler
```svelte
<script lang="ts">
let data = $state({ x: 0, y: 0 });
$inspect(data).with((type, ...values) => {
if (type === 'update') {
console.log('Data updated:', values);
}
});
</script>
```
### Breakpoint on Change
```svelte
<script lang="ts">
let buggyValue = $state(0);
// Pauses debugger when value changes
$inspect(buggyValue).with(() => {
debugger;
});
</script>
```
---
## 🏪 Class-Based State with `$state`
```svelte
<script lang="ts" module>
// Can be in a separate .svelte.ts file
export class Counter {
count = $state(0);
doubled = $derived(this.count * 2);
increment() {
this.count++;
}
decrement() {
this.count--;
}
reset() {
this.count = 0;
}
}
</script>
<script lang="ts">
const counter = new Counter();
</script>
<button onclick={() => counter.decrement()}>-</button>
<span>{counter.count} (doubled: {counter.doubled})</span>
<button onclick={() => counter.increment()}>+</button>
```
---
## ⚠️ Common Mistakes
### ❌ Wrong: Destructuring Loses Reactivity
```svelte
<script lang="ts">
let user = $state({ name: 'John', age: 30 });
// ❌ This breaks reactivity!
let { name, age } = user;
// ✅ Access properties directly
// user.name, user.age
</script>
```
### ❌ Wrong: Effect Without Dependencies
```svelte
<script lang="ts">
let count = $state(0);
// ❌ This runs only once (no reactive reads inside)
$effect(() => {
console.log('This only runs once');
});
// ✅ Read reactive values to create dependencies
$effect(() => {
console.log(`Count is ${count}`);
});
</script>
```
### ❌ Wrong: Mutating Derived Values
```svelte
<script lang="ts">
let items = $state([1, 2, 3]);
let doubled = $derived(items.map(n => n * 2));
// ❌ Cannot mutate derived values!
// doubled.push(8);
// ✅ Mutate the source
items.push(4);
</script>
```
---
## 📊 Runes Comparison Table
| Svelte 4 | Svelte 5 | Purpose |
|----------|----------|---------|
| `export let x` | `let { x } = $props()` | Props |
| `let x = 0` | `let x = $state(0)` | Reactive state |
| `$: y = x * 2` | `let y = $derived(x * 2)` | Computed |
| `$: { ... }` | `$effect(() => { ... })` | Side effects |
| `$$props` | `$props()` with rest | All props |
| `bind:value` | `$bindable()` | Two-way binding |