svelte

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Svelte 5 - Compiler-First Reactive Framework

Svelte 5 - 编译器优先的响应式框架

Overview

概述

Svelte is a compiler-based reactive UI framework that shifts work from runtime to build time. Unlike React/Vue, Svelte compiles components to highly optimized vanilla JavaScript with minimal overhead. Svelte 5 introduces Runes API for explicit, fine-grained reactivity.
Key Features:
  • Runes API: $state, $derived, $effect for explicit reactivity
  • Zero runtime overhead: Compiles to vanilla JS
  • Built-in state management: No external libraries needed
  • SvelteKit: Full-stack framework with SSR/SSG/SPA
  • Write less code: Simple, readable component syntax
  • Exceptional performance: Small bundles, fast runtime
Installation:
bash
undefined
Svelte是一款基于编译器的响应式UI框架,它将工作从运行时转移到构建时。与React/Vue不同,Svelte会将组件编译为高度优化的原生JavaScript,运行时开销极小。Svelte 5引入了Runes API,实现显式、细粒度的响应式能力。

Create new SvelteKit project

核心特性

npm create svelte@latest my-app cd my-app npm install npm run dev
  • Runes API:$state、$derived、$effect实现显式响应式
  • 零运行时开销:编译为原生JS
  • 内置状态管理:无需外部库
  • SvelteKit:支持SSR/SSG/SPA的全栈框架
  • 代码量更少:简洁易读的组件语法
  • 卓越性能:包体积小,运行速度快

Or Svelte only (no SvelteKit)

安装步骤

npm create vite@latest my-app -- --template svelte-ts
undefined
bash
undefined

Svelte 5 Runes API (Modern Approach)

创建新的SvelteKit项目

State Management with $state

svelte
<script lang="ts">
  // Reactive state - automatically tracks changes
  let count = $state(0);
  let user = $state({ name: 'Alice', age: 30 });

  // Arrays and objects are deeply reactive
  let todos = $state<Todo[]>([]);

  function addTodo(text: string) {
    todos.push({ id: Date.now(), text, done: false });
    // No need for todos = [...todos] like React!
  }

  function increment() {
    count++; // Triggers reactivity
  }
</script>

<button onclick={increment}>
  Clicked {count} times
</button>
npm create svelte@latest my-app cd my-app npm install npm run dev

Computed Values with $derived

或仅安装Svelte(不含SvelteKit)

svelte
<script lang="ts">
  let firstName = $state('John');
  let lastName = $state('Doe');

  // Automatically updates when dependencies change
  let fullName = $derived(`${firstName} ${lastName}`);
  let greeting = $derived(`Hello, ${fullName}`);

  // Complex derivations
  let items = $state([1, 2, 3, 4, 5]);
  let total = $derived(items.reduce((sum, n) => sum + n, 0));
  let average = $derived(total / items.length);
</script>

<p>{greeting}</p>
<p>Average: {average.toFixed(2)}</p>
npm create vite@latest my-app -- --template svelte-ts
undefined

Side Effects with $effect

Svelte 5 Runes API(现代方案)

使用$state管理状态

svelte
<script lang="ts">
  let count = $state(0);
  let logs = $state<string[]>([]);

  // Runs when dependencies change
  $effect(() => {
    console.log(`Count is now: ${count}`);
    logs.push(`Count changed to ${count}`);

    // Optional cleanup function
    return () => {
      console.log('Cleaning up previous effect');
    };
  });

  // Effect with external subscriptions
  $effect(() => {
    const interval = setInterval(() => {
      count++;
    }, 1000);

    return () => clearInterval(interval);
  });
</script>
svelte
<script lang="ts">
  // 响应式状态 - 自动追踪变化
  let count = $state(0);
  let user = $state({ name: 'Alice', age: 30 });

  // 数组和对象支持深度响应式
  let todos = $state<Todo[]>([]);

  function addTodo(text: string) {
    todos.push({ id: Date.now(), text, done: false });
    // 无需像React那样写todos = [...todos]!
  }

  function increment() {
    count++; // 触发响应式更新
  }
</script>

<button onclick={increment}>
  已点击 {count} 次
</button>

Component Props with $props

使用$derived计算值

svelte
<script lang="ts">
  interface Props {
    title: string;
    count?: number;
    onUpdate?: (value: number) => void;
  }

  // Type-safe props with defaults
  let { title, count = 0, onUpdate } = $props<Props>();

  // Props are read-only, but can derive from them
  let displayTitle = $derived(title.toUpperCase());
</script>

<h1>{displayTitle}</h1>
<p>Count: {count}</p>
<button onclick={() => onUpdate?.(count + 1)}>
  Increment
</button>
svelte
<script lang="ts">
  let firstName = $state('John');
  let lastName = $state('Doe');

  // 依赖项变化时自动更新
  let fullName = $derived(`${firstName} ${lastName}`);
  let greeting = $derived(`Hello, ${fullName}`);

  // 复杂计算
  let items = $state([1, 2, 3, 4, 5]);
  let total = $derived(items.reduce((sum, n) => sum + n, 0));
  let average = $derived(total / items.length);
</script>

<p>{greeting}</p>
<p>平均值: {average.toFixed(2)}</p>

Two-Way Binding with $bindable

使用$effect处理副作用

svelte
<!-- Child: SearchInput.svelte -->
<script lang="ts">
  interface Props {
    value: string;
    placeholder?: string;
  }

  // Allows parent to bind to this prop
  let { value = $bindable(''), placeholder = 'Search...' } = $props<Props>();
</script>

<input bind:value {placeholder} type="search" />

<!-- Parent.svelte -->
<script lang="ts">
  import SearchInput from './SearchInput.svelte';

  let query = $state('');
  let results = $derived(query ? search(query) : []);
</script>

<SearchInput bind:value={query} />
<p>Found {results.length} results for "{query}"</p>
svelte
<script lang="ts">
  let count = $state(0);
  let logs = $state<string[]>([]);

  // 依赖项变化时执行
  $effect(() => {
    console.log(`当前Count值: ${count}`);
    logs.push(`Count已变更为 ${count}`);

    // 可选的清理函数
    return () => {
      console.log('清理上一次的副作用');
    };
  });

  // 处理外部订阅的副作用
  $effect(() => {
    const interval = setInterval(() => {
      count++;
    }, 1000);

    return () => clearInterval(interval);
  });
</script>

Component Patterns

使用$props定义组件属性

Basic Component Structure

svelte
<!-- Counter.svelte -->
<script lang="ts">
  let count = $state(0);
  let doubled = $derived(count * 2);

  function increment() {
    count++;
  }

  function decrement() {
    count--;
  }
</script>

<div class="counter">
  <button onclick={decrement}>-</button>
  <span>Count: {count} (Doubled: {doubled})</span>
  <button onclick={increment}>+</button>
</div>

<style>
  .counter {
    display: flex;
    gap: 1rem;
    align-items: center;
  }

  button {
    padding: 0.5rem 1rem;
    font-size: 1.2rem;
  }
</style>
svelte
<script lang="ts">
  interface Props {
    title: string;
    count?: number;
    onUpdate?: (value: number) => void;
  }

  // 带默认值的类型安全属性
  let { title, count = 0, onUpdate } = $props<Props>();

  // 属性为只读,但可以基于属性派生新值
  let displayTitle = $derived(title.toUpperCase());
</script>

<h1>{displayTitle}</h1>
<p>计数: {count}</p>
<button onclick={() => onUpdate?.(count + 1)}>
  增加
</button>

Conditional Rendering

使用$bindable实现双向绑定

svelte
<script lang="ts">
  let loggedIn = $state(false);
  let user = $state<User | null>(null);
  let loading = $state(true);
</script>

{#if loading}
  <p>Loading...</p>
{:else if loggedIn && user}
  <p>Welcome, {user.name}!</p>
{:else}
  <p>Please log in</p>
{/if}
svelte
<!-- 子组件: SearchInput.svelte -->
<script lang="ts">
  interface Props {
    value: string;
    placeholder?: string;
  }

  // 允许父组件绑定该属性
  let { value = $bindable(''), placeholder = '搜索...' } = $props<Props>();
</script>

<input bind:value {placeholder} type="search" />

<!-- 父组件: Parent.svelte -->
<script lang="ts">
  import SearchInput from './SearchInput.svelte';

  let query = $state('');
  let results = $derived(query ? search(query) : []);
</script>

<SearchInput bind:value={query} />
<p>为 "{query}" 找到 {results.length} 条结果</p>

Lists and Keyed Each Blocks

组件模式

基础组件结构

svelte
<script lang="ts">
  interface Todo {
    id: number;
    text: string;
    done: boolean;
  }

  let todos = $state<Todo[]>([
    { id: 1, text: 'Learn Svelte', done: true },
    { id: 2, text: 'Build app', done: false }
  ]);

  function toggle(id: number) {
    const todo = todos.find(t => t.id === id);
    if (todo) todo.done = !todo.done;
  }
</script>

<ul>
  {#each todos as todo (todo.id)}
    <li class:done={todo.done}>
      <input
        type="checkbox"
        checked={todo.done}
        onchange={() => toggle(todo.id)}
      />
      {todo.text}
    </li>
  {/each}
</ul>

<style>
  .done {
    text-decoration: line-through;
    opacity: 0.6;
  }
</style>
svelte
<!-- Counter.svelte -->
<script lang="ts">
  let count = $state(0);
  let doubled = $derived(count * 2);

  function increment() {
    count++;
  }

  function decrement() {
    count--;
  }
</script>

<div class="counter">
  <button onclick={decrement}>-</button>
  <span>计数: {count} (翻倍值: {doubled})</span>
  <button onclick={increment}>+</button>
</div>

<style>
  .counter {
    display: flex;
    gap: 1rem;
    align-items: center;
  }

  button {
    padding: 0.5rem 1rem;
    font-size: 1.2rem;
  }
</style>

SvelteKit Framework

条件渲染

Project Structure

my-app/
├── src/
│   ├── routes/
│   │   ├── +page.svelte          # Home page
│   │   ├── +page.ts              # Universal load
│   │   ├── +page.server.ts       # Server load
│   │   ├── +layout.svelte        # Shared layout
│   │   ├── about/
│   │   │   └── +page.svelte      # /about
│   │   └── blog/
│   │       ├── +page.svelte      # /blog
│   │       └── [slug]/
│   │           └── +page.svelte  # /blog/my-post
│   ├── lib/
│   │   ├── components/
│   │   ├── stores/
│   │   └── utils/
│   └── app.html
├── static/                        # Static assets
└── svelte.config.js
svelte
<script lang="ts">
  let loggedIn = $state(false);
  let user = $state<User | null>(null);
  let loading = $state(true);
</script>

{#if loading}
  <p>加载中...</p>
{:else if loggedIn && user}
  <p>欢迎您, {user.name}!</p>
{:else}
  <p>请登录</p>
{/if}

Load Functions (Data Fetching)

列表与带键的Each块

typescript
// src/routes/blog/[slug]/+page.ts
import type { PageLoad } from './$types';

export const load: PageLoad = async ({ params, fetch }) => {
  const response = await fetch(`/api/posts/${params.slug}`);
  const post = await response.json();

  return {
    post
  };
};
svelte
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';

  let { data } = $props<{ data: PageData }>();
</script>

<article>
  <h1>{data.post.title}</h1>
  <div>{@html data.post.content}</div>
</article>
svelte
<script lang="ts">
  interface Todo {
    id: number;
    text: string;
    done: boolean;
  }

  let todos = $state<Todo[]>([
    { id: 1, text: '学习Svelte', done: true },
    { id: 2, text: '开发应用', done: false }
  ]);

  function toggle(id: number) {
    const todo = todos.find(t => t.id === id);
    if (todo) todo.done = !todo.done;
  }
</script>

<ul>
  {#each todos as todo (todo.id)}
    <li class:done={todo.done}>
      <input
        type="checkbox"
        checked={todo.done}
        onchange={() => toggle(todo.id)}
      />
      {todo.text}
    </li>
  {/each}
</ul>

<style>
  .done {
    text-decoration: line-through;
    opacity: 0.6;
  }
</style>

Server-Only Load Functions

SvelteKit框架

项目结构

typescript
// src/routes/admin/+page.server.ts
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ locals }) => {
  if (!locals.user?.isAdmin) {
    throw redirect(303, '/login');
  }

  const users = await db.users.findMany();

  return {
    users
  };
};
my-app/
├── src/
│   ├── routes/
│   │   ├── +page.svelte          # 首页
│   │   ├── +page.ts              # 通用加载逻辑
│   │   ├── +page.server.ts       # 服务端加载逻辑
│   │   ├── +layout.svelte        # 共享布局
│   │   ├── about/
│   │   │   └── +page.svelte      # /about页面
│   │   └── blog/
│   │       ├── +page.svelte      # /blog页面
│   │       └── [slug]/
│   │           └── +page.svelte  # /blog/my-post页面
│   ├── lib/
│   │   ├── components/
│   │   ├── stores/
│   │   └── utils/
│   └── app.html
├── static/                        # 静态资源
└── svelte.config.js

Form Actions

加载函数(数据获取)

typescript
// src/routes/login/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8)
});

export const actions = {
  default: async ({ request, cookies }) => {
    const formData = await request.formData();
    const data = Object.fromEntries(formData);

    const result = schema.safeParse(data);
    if (!result.success) {
      return fail(400, {
        errors: result.error.flatten().fieldErrors
      });
    }

    const user = await authenticateUser(result.data);
    if (!user) {
      return fail(401, { message: 'Invalid credentials' });
    }

    cookies.set('session', user.sessionToken, { path: '/' });
    throw redirect(303, '/dashboard');
  }
} satisfies Actions;
svelte
<!-- src/routes/login/+page.svelte -->
<script lang="ts">
  import type { ActionData } from './$types';

  let { form } = $props<{ form?: ActionData }>();
</script>

<form method="POST">
  <input name="email" type="email" required />
  {#if form?.errors?.email}
    <span class="error">{form.errors.email[0]}</span>
  {/if}

  <input name="password" type="password" required />
  {#if form?.errors?.password}
    <span class="error">{form.errors.password[0]}</span>
  {/if}

  {#if form?.message}
    <p class="error">{form.message}</p>
  {/if}

  <button type="submit">Log In</button>
</form>
typescript
// src/routes/blog/[slug]/+page.ts
import type { PageLoad } from './$types';

export const load: PageLoad = async ({ params, fetch }) => {
  const response = await fetch(`/api/posts/${params.slug}`);
  const post = await response.json();

  return {
    post
  };
};
svelte
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';

  let { data } = $props<{ data: PageData }>();
</script>

<article>
  <h1>{data.post.title}</h1>
  <div>{@html data.post.content}</div>
</article>

State Management Patterns

服务端专属加载函数

Runes-Based Store (Modern)

typescript
// src/lib/stores/cart.svelte.ts
interface CartItem {
  id: number;
  name: string;
  price: number;
  quantity: number;
}

function createCart() {
  let items = $state<CartItem[]>([]);

  let total = $derived(
    items.reduce((sum, item) => sum + item.price * item.quantity, 0)
  );

  let itemCount = $derived(
    items.reduce((sum, item) => sum + item.quantity, 0)
  );

  return {
    get items() { return items; },
    get total() { return total; },
    get itemCount() { return itemCount; },

    addItem(item: Omit<CartItem, 'quantity'>) {
      const existing = items.find(i => i.id === item.id);
      if (existing) {
        existing.quantity++;
      } else {
        items.push({ ...item, quantity: 1 });
      }
    },

    removeItem(id: number) {
      items = items.filter(i => i.id !== id);
    },

    clear() {
      items = [];
    }
  };
}

export const cart = createCart();
svelte
<!-- Usage -->
<script lang="ts">
  import { cart } from '$lib/stores/cart.svelte';
</script>

<div>
  <p>Items: {cart.itemCount}</p>
  <p>Total: ${cart.total.toFixed(2)}</p>

  <button onclick={() => cart.addItem({ id: 1, name: 'Widget', price: 9.99 })}>
    Add Widget
  </button>
</div>
typescript
// src/routes/admin/+page.server.ts
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ locals }) => {
  if (!locals.user?.isAdmin) {
    throw redirect(303, '/login');
  }

  const users = await db.users.findMany();

  return {
    users
  };
};

Legacy Svelte Store (Svelte 4 Style)

表单操作

typescript
// src/lib/stores/user.ts
import { writable, derived } from 'svelte/store';

interface User {
  id: number;
  name: string;
  email: string;
}

function createUserStore() {
  const { subscribe, set, update } = writable<User | null>(null);

  return {
    subscribe,
    login: (user: User) => set(user),
    logout: () => set(null),
    updateName: (name: string) => update(u => u ? { ...u, name } : null)
  };
}

export const user = createUserStore();

// Derived store
export const isLoggedIn = derived(user, $user => $user !== null);
svelte
<!-- Usage with $ prefix -->
<script lang="ts">
  import { user, isLoggedIn } from '$lib/stores/user';
</script>

{#if $isLoggedIn}
  <p>Welcome, {$user?.name}!</p>
{:else}
  <p>Please log in</p>
{/if}
typescript
// src/routes/login/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8)
});

export const actions = {
  default: async ({ request, cookies }) => {
    const formData = await request.formData();
    const data = Object.fromEntries(formData);

    const result = schema.safeParse(data);
    if (!result.success) {
      return fail(400, {
        errors: result.error.flatten().fieldErrors
      });
    }

    const user = await authenticateUser(result.data);
    if (!user) {
      return fail(401, { message: '凭据无效' });
    }

    cookies.set('session', user.sessionToken, { path: '/' });
    throw redirect(303, '/dashboard');
  }
} satisfies Actions;
svelte
<!-- src/routes/login/+page.svelte -->
<script lang="ts">
  import type { ActionData } from './$types';

  let { form } = $props<{ form?: ActionData }>();
</script>

<form method="POST">
  <input name="email" type="email" required />
  {#if form?.errors?.email}
    <span class="error">{form.errors.email[0]}</span>
  {/if}

  <input name="password" type="password" required />
  {#if form?.errors?.password}
    <span class="error">{form.errors.password[0]}</span>
  {/if}

  {#if form?.message}
    <p class="error">{form.message}</p>
  {/if}

  <button type="submit">登录</button>
</form>

Animations and Transitions

状态管理模式

Built-in Transitions

基于Runes的Store(现代方案)

svelte
<script lang="ts">
  import { fade, slide, fly, scale } from 'svelte/transition';
  import { quintOut } from 'svelte/easing';

  let visible = $state(true);
</script>

<button onclick={() => visible = !visible}>
  Toggle
</button>

{#if visible}
  <div transition:fade={{ duration: 300 }}>
    Fades in and out
  </div>

  <div transition:slide={{ duration: 300 }}>
    Slides in and out
  </div>

  <div transition:fly={{ y: 200, duration: 500, easing: quintOut }}>
    Flies in from bottom
  </div>
{/if}
typescript
// src/lib/stores/cart.svelte.ts
interface CartItem {
  id: number;
  name: string;
  price: number;
  quantity: number;
}

function createCart() {
  let items = $state<CartItem[]>([]);

  let total = $derived(
    items.reduce((sum, item) => sum + item.price * item.quantity, 0)
  );

  let itemCount = $derived(
    items.reduce((sum, item) => sum + item.quantity, 0)
  );

  return {
    get items() { return items; },
    get total() { return total; },
    get itemCount() { return itemCount; },

    addItem(item: Omit<CartItem, 'quantity'>) {
      const existing = items.find(i => i.id === item.id);
      if (existing) {
        existing.quantity++;
      } else {
        items.push({ ...item, quantity: 1 });
      }
    },

    removeItem(id: number) {
      items = items.filter(i => i.id !== id);
    },

    clear() {
      items = [];
    }
  };
}

export const cart = createCart();
svelte
<!-- 使用示例 -->
<script lang="ts">
  import { cart } from '$lib/stores/cart.svelte';
</script>

<div>
  <p>商品数量: {cart.itemCount}</p>
  <p>总价: ${cart.total.toFixed(2)}</p>

  <button onclick={() => cart.addItem({ id: 1, name: 'Widget', price: 9.99 })}>
    添加Widget
  </button>
</div>

Custom Transitions

传统Svelte Store(Svelte 4风格)

typescript
// src/lib/transitions.ts
export function blur(node: HTMLElement, { duration = 300 }) {
  return {
    duration,
    css: (t: number) => `
      opacity: ${t};
      filter: blur(${(1 - t) * 10}px);
    `
  };
}
svelte
<script lang="ts">
  import { blur } from '$lib/transitions';
  let show = $state(true);
</script>

{#if show}
  <div transition:blur={{ duration: 500 }}>
    Custom blur transition
  </div>
{/if}
typescript
// src/lib/stores/user.ts
import { writable, derived } from 'svelte/store';

interface User {
  id: number;
  name: string;
  email: string;
}

function createUserStore() {
  const { subscribe, set, update } = writable<User | null>(null);

  return {
    subscribe,
    login: (user: User) => set(user),
    logout: () => set(null),
    updateName: (name: string) => update(u => u ? { ...u, name } : null)
  };
}

export const user = createUserStore();

// 派生Store
export const isLoggedIn = derived(user, $user => $user !== null);
svelte
<!-- 使用$前缀访问 -->
<script lang="ts">
  import { user, isLoggedIn } from '$lib/stores/user';
</script>

{#if $isLoggedIn}
  <p>欢迎您, {$user?.name}!</p>
{:else}
  <p>请登录</p>
{/if}

Animate Directive (FLIP Animations)

动画与过渡效果

内置过渡效果

svelte
<script lang="ts">
  import { flip } from 'svelte/animate';
  import { quintOut } from 'svelte/easing';

  let items = $state([1, 2, 3, 4, 5]);

  function shuffle() {
    items = items.sort(() => Math.random() - 0.5);
  }
</script>

<button onclick={shuffle}>Shuffle</button>

<ul>
  {#each items as item (item)}
    <li animate:flip={{ duration: 500, easing: quintOut }}>
      {item}
    </li>
  {/each}
</ul>
svelte
<script lang="ts">
  import { fade, slide, fly, scale } from 'svelte/transition';
  import { quintOut } from 'svelte/easing';

  let visible = $state(true);
</script>

<button onclick={() => visible = !visible}>
  切换显示
</button>

{#if visible}
  <div transition:fade={{ duration: 300 }}>
    淡入淡出效果
  </div>

  <div transition:slide={{ duration: 300 }}>
    滑入滑出效果
  </div>

  <div transition:fly={{ y: 200, duration: 500, easing: quintOut }}>
    从底部飞入效果
  </div>
{/if}

Advanced Features

自定义过渡效果

Actions (Element Behaviors)

typescript
// src/lib/actions.ts
export function clickOutside(node: HTMLElement, callback: () => void) {
  const handleClick = (event: MouseEvent) => {
    if (!node.contains(event.target as Node)) {
      callback();
    }
  };

  document.addEventListener('click', handleClick, true);

  return {
    destroy() {
      document.removeEventListener('click', handleClick, true);
    }
  };
}
svelte
<script lang="ts">
  import { clickOutside } from '$lib/actions';

  let showMenu = $state(false);
</script>

<button onclick={() => showMenu = !showMenu}>
  Menu
</button>

{#if showMenu}
  <div
    class="menu"
    use:clickOutside={() => showMenu = false}
  >
    <ul>
      <li>Item 1</li>
      <li>Item 2</li>
    </ul>
  </div>
{/if}
typescript
// src/lib/transitions.ts
export function blur(node: HTMLElement, { duration = 300 }) {
  return {
    duration,
    css: (t: number) => `
      opacity: ${t};
      filter: blur(${(1 - t) * 10}px);
    `
  };
}
svelte
<script lang="ts">
  import { blur } from '$lib/transitions';
  let show = $state(true);
</script>

{#if show}
  <div transition:blur={{ duration: 500 }}>
    自定义模糊过渡效果
  </div>
{/if}

Slots and Component Composition

Animate指令(FLIP动画)

svelte
<!-- Card.svelte -->
<script lang="ts">
  let { title } = $props<{ title?: string }>();
</script>

<div class="card">
  <header>
    {#if title}
      <h2>{title}</h2>
    {:else}
      <slot name="header">Default Header</slot>
    {/if}
  </header>

  <main>
    <slot>Default content</slot>
  </main>

  <footer>
    <slot name="footer" />
  </footer>
</div>

<!-- Usage -->
<Card title="My Card">
  <p>This is the main content</p>

  <div slot="footer">
    <button>Action</button>
  </div>
</Card>
svelte
<script lang="ts">
  import { flip } from 'svelte/animate';
  import { quintOut } from 'svelte/easing';

  let items = $state([1, 2, 3, 4, 5]);

  function shuffle() {
    items = items.sort(() => Math.random() - 0.5);
  }
</script>

<button onclick={shuffle}>随机排序</button>

<ul>
  {#each items as item (item)}
    <li animate:flip={{ duration: 500, easing: quintOut }}>
      {item}
    </li>
  {/each}
</ul>

Special Elements

高级特性

Actions(元素行为)

svelte
<script lang="ts">
  let Component = $state<typeof import('./A.svelte').default | typeof import('./B.svelte').default>(A);
  let tag = $state<'div' | 'span'>('div');
</script>

<!-- Dynamic component -->
<svelte:component this={Component} />

<!-- Dynamic HTML element -->
<svelte:element this={tag}>
  Content
</svelte:element>

<!-- Window events -->
<svelte:window
  onresize={() => console.log('Window resized')}
  onkeydown={(e) => console.log(e.key)}
/>

<!-- Document head -->
<svelte:head>
  <title>My Page</title>
  <meta name="description" content="Description" />
</svelte:head>
typescript
// src/lib/actions.ts
export function clickOutside(node: HTMLElement, callback: () => void) {
  const handleClick = (event: MouseEvent) => {
    if (!node.contains(event.target as Node)) {
      callback();
    }
  };

  document.addEventListener('click', handleClick, true);

  return {
    destroy() {
      document.removeEventListener('click', handleClick, true);
    }
  };
}
svelte
<script lang="ts">
  import { clickOutside } from '$lib/actions';

  let showMenu = $state(false);
</script>

<button onclick={() => showMenu = !showMenu}>
  菜单
</button>

{#if showMenu}
  <div
    class="menu"
    use:clickOutside={() => showMenu = false}
  >
    <ul>
      <li>选项1</li>
      <li>选项2</li>
    </ul>
  </div>
{/if}

Migration from React/Vue

插槽与组件组合

React to Svelte 5

ReactSvelte 5Notes
useState(0)
$state(0)
Direct state
useMemo(() => x * 2, [x])
$derived(x * 2)
Auto-tracked
useEffect(() => {...}, [x])
$effect(() => {...})
Auto deps
props.name
let { name } = $props()
Destructure
{show && <div />}
{#if show}<div />{/if}
Explicit
svelte
<!-- Card.svelte -->
<script lang="ts">
  let { title } = $props<{ title?: string }>();
</script>

<div class="card">
  <header>
    {#if title}
      <h2>{title}</h2>
    {:else}
      <slot name="header">默认头部</slot>
    {/if}
  </header>

  <main>
    <slot>默认内容</slot>
  </main>

  <footer>
    <slot name="footer" />
  </footer>
</div>

<!-- 使用示例 -->
<Card title="我的卡片">
  <p>这是主要内容</p>

  <div slot="footer">
    <button>操作按钮</button>
  </div>
</Card>

Vue 3 to Svelte 5

特殊元素

Vue 3Svelte 5Notes
ref(0)
$state(0)
Direct state
computed(() => x * 2)
$derived(x * 2)
Similar
watch(() => x, ...)
$effect(() => {...})
Auto-tracked
defineProps<{ name: string }>()
let { name } = $props<Props>()
Type-safe
v-if="show"
{#if show}
Block syntax
svelte
<script lang="ts">
  let Component = $state<typeof import('./A.svelte').default | typeof import('./B.svelte').default>(A);
  let tag = $state<'div' | 'span'>('div');
</script>

<!-- 动态组件 -->
<svelte:component this={Component} />

<!-- 动态HTML元素 -->
<svelte:element this={tag}>
  内容
</svelte:element>

<!-- 窗口事件 -->
<svelte:window
  onresize={() => console.log('窗口已调整大小')}
  onkeydown={(e) => console.log(e.key)}
/>

<!-- 文档头部 -->
<svelte:head>
  <title>我的页面</title>
  <meta name="description" content="页面描述" />
</svelte:head>

Performance Best Practices

从React/Vue迁移

React迁移到Svelte 5

  1. Use $derived over $effect when only computed values are needed
  2. Avoid unnecessary $effect - only for side effects, not computations
  3. Leverage compiler optimizations - Svelte does most work at build time
  4. Use keyed each blocks -
    {#each items as item (item.id)}
  5. Lazy load components -
    const Component = await import('./Heavy.svelte')
  6. SSR for initial load - Use SvelteKit's SSR capabilities
  7. Optimize bundles - Use adapters for deployment targets
ReactSvelte 5说明
useState(0)
$state(0)
直接定义状态
useMemo(() => x * 2, [x])
$derived(x * 2)
自动追踪依赖
useEffect(() => {...}, [x])
$effect(() => {...})
自动识别依赖
props.name
let { name } = $props()
解构获取属性
{show && <div />}
{#if show}<div />{/if}
显式条件语法

Testing

Vue 3迁移到Svelte 5

Unit Tests with Vitest

typescript
// Counter.test.ts
import { render, fireEvent } from '@testing-library/svelte';
import { expect, test } from 'vitest';
import Counter from './Counter.svelte';

test('increments counter on click', async () => {
  const { getByText } = render(Counter);

  const button = getByText('+');
  await fireEvent.click(button);

  expect(getByText(/Count: 1/)).toBeInTheDocument();
});
Vue 3Svelte 5说明
ref(0)
$state(0)
直接定义状态
computed(() => x * 2)
$derived(x * 2)
用法类似
watch(() => x, ...)
$effect(() => {...})
自动追踪依赖
defineProps<{ name: string }>()
let { name } = $props<Props>()
类型安全
v-if="show"
{#if show}
块级语法

E2E with Playwright

性能最佳实践

typescript
// tests/login.test.ts
import { expect, test } from '@playwright/test';

test('user can log in', async ({ page }) => {
  await page.goto('/login');

  await page.fill('input[name="email"]', 'user@example.com');
  await page.fill('input[name="password"]', 'password123');
  await page.click('button[type="submit"]');

  await expect(page).toHaveURL('/dashboard');
  await expect(page.locator('text=Welcome')).toBeVisible();
});
  1. 优先使用$derived而非$effect:仅需计算值时选择$derived
  2. 避免不必要的$effect:仅用于处理副作用,不用于计算
  3. 利用编译器优化:Svelte会在构建时完成大部分工作
  4. 使用带键的Each块
    {#each items as item (item.id)}
  5. 懒加载组件
    const Component = await import('./Heavy.svelte')
  6. 使用SSR优化初始加载:借助SvelteKit的SSR能力
  7. 优化包体积:针对部署目标选择合适的适配器

Deployment

测试

Adapters

使用Vitest进行单元测试

javascript
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel'; // or adapter-node, adapter-static

export default {
  kit: {
    adapter: adapter()
  }
};
Available Adapters:
  • adapter-auto
    : Auto-detect platform
  • adapter-vercel
    : Vercel deployment
  • adapter-node
    : Node.js server
  • adapter-static
    : Static site generation
  • adapter-cloudflare
    : Cloudflare Pages/Workers
  • adapter-netlify
    : Netlify deployment
typescript
// Counter.test.ts
import { render, fireEvent } from '@testing-library/svelte';
import { expect, test } from 'vitest';
import Counter from './Counter.svelte';

test('点击按钮增加计数', async () => {
  const { getByText } = render(Counter);

  const button = getByText('+');
  await fireEvent.click(button);

  expect(getByText(/计数: 1/)).toBeInTheDocument();
});

Resources

使用Playwright进行端到端测试

typescript
// tests/login.test.ts
import { expect, test } from '@playwright/test';

test('用户可以登录', async ({ page }) => {
  await page.goto('/login');

  await page.fill('input[name="email"]', 'user@example.com');
  await page.fill('input[name="password"]', 'password123');
  await page.click('button[type="submit"]');

  await expect(page).toHaveURL('/dashboard');
  await expect(page.locator('text=欢迎')).toBeVisible();
});

Summary

部署

适配器配置

  • Svelte 5 uses Runes API ($state, $derived, $effect) for explicit reactivity
  • Compiler-first - shifts work to build time for minimal runtime overhead
  • SvelteKit provides full-stack capabilities with SSR/SSG/SPA modes
  • Write less code - simpler syntax than React/Vue
  • Exceptional performance - small bundles, fast runtime
  • Migration-friendly - Svelte 4 and 5 can coexist
  • Type-safe - First-class TypeScript support
javascript
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel'; // 或adapter-node、adapter-static

export default {
  kit: {
    adapter: adapter()
  }
};
可用适配器:
  • adapter-auto
    : 自动检测部署平台
  • adapter-vercel
    : 部署到Vercel
  • adapter-node
    : 部署为Node.js服务
  • adapter-static
    : 生成静态站点
  • adapter-cloudflare
    : 部署到Cloudflare Pages/Workers
  • adapter-netlify
    : 部署到Netlify

资源链接

总结

  • Svelte 5 使用Runes API($state、$derived、$effect)实现显式响应式
  • 编译器优先:将工作转移到构建时,最小化运行时开销
  • SvelteKit 提供全栈能力,支持SSR/SSG/SPA模式
  • 代码量更少:语法比React/Vue更简洁
  • 性能卓越:包体积小,运行速度快
  • 迁移友好:Svelte 4和5可以共存
  • 类型安全:原生支持TypeScript