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

8.3 KiB
Raw Blame History

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

<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

<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

<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

<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

<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

<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

<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

<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

<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

<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

<script lang="ts">
  $effect(() => {
    console.log($effect.tracking()); // true
  });
  
  function regularFunction() {
    console.log($effect.tracking()); // false
  }
</script>

$effect.root - Untracked Effect Scope

<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

<script lang="ts">
  let { name, age }: { name: string; age: number } = $props();
</script>

<p>{name} is {age} years old</p>

Optional Props with Defaults

<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

<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)

<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

<!-- 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

<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

<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

<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

<script lang="ts">
  let buggyValue = $state(0);
  
  // Pauses debugger when value changes
  $inspect(buggyValue).with(() => {
    debugger;
  });
</script>

🏪 Class-Based State with $state

<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

<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

<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

<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