Svelte 是一种构建用户界面的创新方法。与 React 或 Vue 不同,Svelte 将工作从浏览器转移到编译步骤,生成高度优化的原生 JavaScript,没有虚拟 DOM 开销。随着 Svelte 5 引入 runes 和 SvelteKit 提供全栈框架,Svelte 已成为 2026 年现代 Web 开发的引人注目的选择。
Svelte 在构建时将组件编译为高效的命令式代码,消除虚拟 DOM diff。其响应式系统内置于语言中,包括响应式声明($:)和 Svelte 5 runes($state、$derived、$effect)。SvelteKit 提供基于文件的路由、SSR、表单 actions 和渐进增强。
- Svelte 编译为原生 JS,没有运行时框架开销
- 响应式是内置于语言的,而非库 API
- Svelte 5 runes 提供细粒度的通用响应式
- SvelteKit 提供 SSR、基于文件的路由和表单 actions
- 过渡和动画是内置指令的一等公民
- Store 提供简单的、框架集成的状态管理
Svelte 响应式系统
Svelte 在编译时跟踪响应式依赖。当你给 let 声明的变量赋值时,Svelte 会自动更新 DOM。响应式声明($:)让你定义计算值和副作用。
响应式声明与语句
<script>
let count = 0;
// Reactive declaration: recomputes when count changes
$: doubled = count * 2;
$: quadrupled = doubled * 2;
// Reactive statement: runs side effect
$: if (count >= 10) {
console.log("Count reached 10!");
count = 0;
}
// Reactive block
$: {
console.log("count is", count);
console.log("doubled is", doubled);
}
function increment() {
count += 1; // assignment triggers reactivity
}
</script>
<button on:click={increment}>
Clicked {count} times
</button>
<p>{count} x 2 = {doubled}</p>
<p>{count} x 4 = {quadrupled}</p>响应式赋值
在 Svelte 中,赋值触发响应式。编译器会对每个赋值进行插桩以调度 DOM 更新。数组和对象变更需要重新赋值才能触发更新。
<script>
let items = ["apple", "banana"];
function addItem() {
// Reassignment triggers reactivity
items = [...items, "cherry"];
// This will NOT trigger reactivity:
// items.push("cherry");
// Idiomatic: push then reassign
items.push("date");
items = items; // trigger update
}
let user = { name: "Alice", age: 30 };
function birthday() {
user.age += 1; // property assignment works
}
</script>组件:Props、事件、Slots 与生命周期
Svelte 组件写在 .svelte 文件中,将 HTML、CSS 和 JavaScript 组合在一个文件中。Props 向下传递,事件向上冒泡,slots 允许内容组合。
组件 Props
<!-- UserCard.svelte -->
<script>
export let name; // required prop
export let age = 25; // optional with default
export let role = "user";
</script>
<div class="card">
<h3>{name}</h3>
<p>Age: {age} | Role: {role}</p>
</div>
<style>
.card { padding: 1rem; border: 1px solid #ddd; }
</style>
<!-- Parent.svelte -->
<script>
import UserCard from "./UserCard.svelte";
</script>
<UserCard name="Alice" age={30} role="admin" />
<UserCard name="Bob" />自定义事件
组件使用 createEventDispatcher 分发自定义事件。父组件使用 on: 指令监听。
<!-- SearchInput.svelte -->
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
let query = "";
function handleInput() {
dispatch("search", { query });
}
</script>
<input bind:value={query} on:input={handleInput}
placeholder="Search..." />
<!-- App.svelte -->
<script>
import SearchInput from "./SearchInput.svelte";
function onSearch(event) {
console.log("Searching:", event.detail.query);
}
</script>
<SearchInput on:search={onSearch} />Slots 与命名 Slots
Slots 允许父组件向子组件注入内容。命名 slots 提供多个插入点。
<!-- Modal.svelte -->
<script>
export let title = "Dialog";
</script>
<div class="modal-backdrop">
<div class="modal">
<header>
<slot name="header"><h2>{title}</h2></slot>
</header>
<main><slot /></main>
<footer>
<slot name="footer">
<button>Close</button>
</slot>
</footer>
</div>
</div>
<!-- Usage -->
<Modal>
<h2 slot="header">Confirm Delete</h2>
<p>Are you sure?</p>
<div slot="footer">
<button>Cancel</button>
<button>Delete</button>
</div>
</Modal>生命周期函数
Svelte 提供 onMount、onDestroy、beforeUpdate、afterUpdate 和 tick 来管理组件生命周期。
<script>
import { onMount, onDestroy, beforeUpdate,
afterUpdate, tick } from "svelte";
let data = null;
onMount(async () => {
const res = await fetch("/api/data");
data = await res.json();
return () => { /* cleanup */ };
});
onDestroy(() => { /* remove listeners */ });
beforeUpdate(() => { /* before DOM update */ });
afterUpdate(() => { /* after DOM update */ });
async function handleClick() {
data = newValue;
await tick(); // wait for DOM to update
}
</script>Svelte Stores
Store 是 Svelte 内置的状态管理方案。它们是可订阅和更新的响应式对象。自动订阅语法($store)使其使用非常流畅。
可写 Store
// stores.js
import { writable } from "svelte/store";
export const count = writable(0);
export const user = writable({ name: "", loggedIn: false });
// Component.svelte
<script>
import { count, user } from "./stores.js";
// $ prefix auto-subscribes and unsubscribes
function increment() {
$count += 1;
// equivalent to: count.update(n => n + 1);
}
</script>
<p>Count: {$count}</p>
<p>User: {$user.name}</p>
<button on:click={increment}>+1</button>可读 Store 与派生 Store
import { readable, derived } from "svelte/store";
// Readable store: external data source
export const time = readable(new Date(), (set) => {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return () => clearInterval(interval);
});
// Derived store: computed from other stores
export const elapsed = derived(time, ($time) =>
Math.round(($time.getTime() - start) / 1000)
);
// Derived from multiple stores
export const summary = derived(
[count, user],
([$count, $user]) =>
`\${$user.name} clicked \${$count} times`
);自定义 Store
自定义 store 通过包装可写 store 并暴露受控 API 来封装逻辑。
import { writable } from "svelte/store";
function createTodoStore() {
const { subscribe, update, set } = writable([]);
return {
subscribe,
add: (text) => update(todos =>
[...todos, { id: Date.now(), text, done: false }]
),
toggle: (id) => update(todos =>
todos.map(t =>
t.id === id ? { ...t, done: !t.done } : t
)
),
remove: (id) => update(todos =>
todos.filter(t => t.id !== id)
),
reset: () => set([])
};
}
export const todos = createTodoStore();SvelteKit:路由、布局与 Load 函数
SvelteKit 是 Svelte 的官方全栈框架,提供基于文件的路由、服务端渲染、API 路由和强大的 load 函数数据获取系统。
基于文件的路由
SvelteKit 使用文件系统进行路由。src/routes 中的每个 +page.svelte 文件都成为一个页面。
src/routes/
+page.svelte → /
+layout.svelte → shared layout
about/+page.svelte → /about
blog/
+page.svelte → /blog
[slug]/
+page.svelte → /blog/:slug
+page.server.ts → server load
api/users/
+server.ts → /api/users
(auth)/
login/+page.svelte → /login
register/+page.svelte → /register布局
布局包裹页面并在导航间持久存在。嵌套布局允许在不同路由级别共享 UI。
<!-- src/routes/+layout.svelte -->
<script>
import Header from "$lib/Header.svelte";
import Footer from "$lib/Footer.svelte";
</script>
<Header />
<main><slot /></main>
<Footer />
<!-- src/routes/dashboard/+layout.svelte -->
<script>
import Sidebar from "$lib/Sidebar.svelte";
</script>
<div class="dashboard">
<Sidebar />
<div class="content"><slot /></div>
</div>Load 函数
Load 函数在渲染前获取数据。它们在 SSR 期间在服务器上运行,在客户端导航时在浏览器中运行。
// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad } from "./$types";
import { error } from "@sveltejs/kit";
export const load: PageServerLoad = async ({
params, fetch
}) => {
const res = await fetch(
`/api/posts/\${params.slug}`
);
if (!res.ok) throw error(404, "Not found");
return { post: await res.json() };
};
// src/routes/blog/[slug]/+page.svelte
<script>
export let data;
</script>
<article>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
</article>表单 Actions 与渐进增强
SvelteKit 表单 actions 在服务器上处理表单提交。它们无需 JavaScript 即可工作,并可通过 use:enhance 渐进增强。
// src/routes/login/+page.server.ts
import { fail, redirect } from "@sveltejs/kit";
export const actions = {
default: async ({ request, cookies }) => {
const data = await request.formData();
const email = data.get("email");
const password = data.get("password");
if (!email)
return fail(400, { email, missing: true });
const user = await authenticate(email, password);
if (!user)
return fail(401, { email, incorrect: true });
cookies.set("session", user.token, { path: "/" });
throw redirect(303, "/dashboard");
}
};
// src/routes/login/+page.svelte
<script>
import { enhance } from "$app/forms";
export let form;
</script>
<form method="POST" use:enhance>
<input name="email" value={form?.email ?? ""} />
{#if form?.missing}
<p class="error">Email is required</p>
{/if}
<input name="password" type="password" />
<button>Log In</button>
</form>过渡与动画
Svelte 通过指令内置支持过渡和动画。元素可以用最少的代码平滑地进入和离开 DOM。
内置过渡
<script>
import { fade, fly, slide, scale }
from "svelte/transition";
import { quintOut } from "svelte/easing";
let visible = true;
</script>
<button on:click={() => visible = !visible}>
Toggle
</button>
{#if visible}
<div transition:fade={{ duration: 300 }}>
Fades in and out
</div>
<div transition:fly={{ y: 200, duration: 500,
easing: quintOut }}>
Flies in from below
</div>
<div in:fly={{ x: -200 }} out:fade>
Flies in, fades out
</div>
<div transition:slide={{ duration: 300 }}>
Slides open and closed
</div>
{/if}自定义过渡
通过返回包含 css 或 tick 函数的对象来创建自定义过渡。
<script>
function typewriter(node, { speed = 1 }) {
const text = node.textContent;
const duration = text.length / (speed * 0.01);
return {
duration,
tick: (t) => {
const i = Math.trunc(text.length * t);
node.textContent = text.slice(0, i);
}
};
}
</script>
{#if visible}
<p transition:typewriter={{ speed: 2 }}>
This text types itself out!
</p>
{/if}animate 指令
animate 指令在键控 each 块中元素位置变化时平滑移动元素。
<script>
import { flip } from "svelte/animate";
import { fade } from "svelte/transition";
let list = [1, 2, 3, 4, 5];
function shuffle() {
list = list.sort(() => Math.random() - 0.5);
}
</script>
<button on:click={shuffle}>Shuffle</button>
{#each list as item (item)}
<div animate:flip={{ duration: 300 }}
transition:fade>{item}</div>
{/each}Actions(use: 指令)
Actions 是元素挂载到 DOM 时运行的函数。它们适用于集成第三方库、添加事件监听器或实现可复用的 DOM 行为。
<script>
// Tooltip action
function tooltip(node, text) {
let tip;
function show() {
tip = document.createElement("div");
tip.textContent = text;
tip.style.cssText = `position:absolute;
background:#333;color:#fff;padding:4px 8px;
border-radius:4px;font-size:12px`;
node.appendChild(tip);
}
function hide() { tip?.remove(); }
node.addEventListener("mouseenter", show);
node.addEventListener("mouseleave", hide);
return {
update(newText) { text = newText; },
destroy() {
tip?.remove();
node.removeEventListener("mouseenter", show);
node.removeEventListener("mouseleave", hide);
}
};
}
// Click-outside action
function clickOutside(node, callback) {
function onClick(e) {
if (!node.contains(e.target)) callback();
}
document.addEventListener("click", onClick, true);
return {
destroy() {
document.removeEventListener("click",
onClick, true);
}
};
}
</script>
<button use:tooltip={"Click me!"}>Hover</button>
<div use:clickOutside={() => open = false}>
Dropdown content
</div>Context API
Context API 在组件树中传递数据而无需 prop 逐层传递。与 store 不同,context 作用域限定于组件子树。
<!-- ThemeProvider.svelte -->
<script>
import { setContext } from "svelte";
import { writable } from "svelte/store";
const theme = writable("light");
setContext("theme", {
theme,
toggle: () => theme.update(t =>
t === "light" ? "dark" : "light"
)
});
</script>
<slot />
<!-- DeepChild.svelte -->
<script>
import { getContext } from "svelte";
const { theme, toggle } = getContext("theme");
</script>
<p>Current theme: {$theme}</p>
<button on:click={toggle}>Toggle Theme</button>Svelte 5 Runes
Svelte 5 引入了 runes,一组编译器级别的原语,提供通用的细粒度响应式。Runes 替代了响应式声明($:)、export let props 和许多 store 用例。
$state 与 $derived
<script>
// $state: reactive state (replaces let)
let count = $state(0);
let items = $state(["apple", "banana"]);
// $derived: computed value (replaces $:)
let doubled = $derived(count * 2);
let total = $derived(items.length);
// Complex derived expression
let summary = $derived.by(() => {
if (count > 10) return "Many clicks";
if (count > 5) return "Some clicks";
return "Few clicks";
});
// Deep reactivity in Svelte 5
function addItem() {
items.push("cherry"); // works in Svelte 5!
}
</script>
<button onclick={() => count++}>
{count} (doubled: {doubled})
</button>
<p>{summary} — {total} items</p>$effect
$effect rune 替代响应式语句和 onMount 用于副作用。它自动跟踪依赖并在变化时重新运行。
<script>
let count = $state(0);
let title = $state("My App");
// Auto-tracks dependencies
$effect(() => {
document.title = `\${title} (\${count})`;
});
// With cleanup
$effect(() => {
const id = setInterval(() => count++, 1000);
return () => clearInterval(id);
});
// Pre-effect (runs before DOM update)
$effect.pre(() => {
console.log("about to update DOM");
});
</script>$props
$props rune 替代 export let 来在 Svelte 5 中声明组件 props。
<!-- Svelte 5 component with $props -->
<script>
// Destructure with defaults
let { name, age = 25, role = "user", ...rest }
= $props();
// With TypeScript:
// interface Props {
// name: string;
// age?: number;
// role?: "user" | "admin";
// }
// let { name, age = 25, role = "user" }
// = $props<Props>();
</script>
<div {...rest}>
<h3>{name}</h3>
<p>Age: {age} | Role: {role}</p>
</div>服务端渲染与 Hydration
SvelteKit 默认在服务器端渲染页面并在客户端进行 hydration。这提供了快速的初始加载、良好的 SEO 和渐进增强。
// src/routes/+page.server.ts
export const load = async ({ fetch }) => {
const res = await fetch("/api/products");
return { products: await res.json() };
};
// Page options
export const prerender = true; // static at build
export const ssr = true; // default: SSR on
export const csr = true; // default: hydrate
// +page.svelte
<script>
import { browser } from "$app/environment";
export let data;
if (browser) {
console.log("Client-side hydrated!");
}
</script>
{#each data.products as product}
<div>{product.name} — ${product.price}</div>
{/each}组件组合模式
Svelte 支持多种组合模式来构建可复用、可维护的组件架构。
<!-- Compound Components: Tabs -->
<!-- Tabs.svelte -->
<script>
import { setContext } from "svelte";
import { writable } from "svelte/store";
const activeTab = writable(0);
setContext("tabs", { activeTab });
</script>
<div class="tabs"><slot /></div>
<!-- Tab.svelte -->
<script>
import { getContext } from "svelte";
export let index;
const { activeTab } = getContext("tabs");
</script>
<button on:click={() => $activeTab = index}
class:active={$activeTab === index}>
<slot />
</button>
<!-- TabPanel.svelte -->
<script>
import { getContext } from "svelte";
export let index;
const { activeTab } = getContext("tabs");
</script>
{#if $activeTab === index}
<div class="panel"><slot /></div>
{/if}
<!-- Usage -->
<Tabs>
<Tab index={0}>General</Tab>
<Tab index={1}>Settings</Tab>
<TabPanel index={0}>General content</TabPanel>
<TabPanel index={1}>Settings content</TabPanel>
</Tabs>使用 Vitest 和 Playwright 测试
Svelte 组件可以使用 Vitest 进行单元和集成测试,使用 Playwright 进行端到端测试。
// counter.test.ts (Vitest + testing-library)
import { render, fireEvent } from
"@testing-library/svelte";
import { describe, it, expect } from "vitest";
import Counter from "./Counter.svelte";
describe("Counter", () => {
it("increments on click", async () => {
const { getByText } = render(Counter,
{ props: { initial: 0 } });
const btn = getByText("Count: 0");
await fireEvent.click(btn);
expect(getByText("Count: 1")).toBeTruthy();
});
});
// e2e/home.test.ts (Playwright)
import { test, expect } from "@playwright/test";
test("home page loads", async ({ page }) => {
await page.goto("/");
await expect(page.locator("h1"))
.toContainText("Welcome");
await page.click("button:text(\"Increment\")");
await expect(page.locator("[data-count]"))
.toHaveAttribute("data-count", "1");
});性能对比
由于编译时方法和没有虚拟 DOM 开销,Svelte 在基准测试中始终优于 React 和 Vue。
| 指标 | Svelte | React | Vue |
|---|---|---|---|
| Bundle Size (min+gzip) | 1.6 KB | 44+ KB | 34+ KB |
| Runtime Overhead | None (compiled) | Virtual DOM | Virtual DOM |
| Startup Time (TTI) | ~50ms | ~120ms | ~100ms |
| Memory Usage | Low | Medium-High | Medium |
| DOM Update Speed | Direct mutation | Reconciliation | Proxy-based |
| JS Benchmark Score | 1.02 | 1.38 | 1.21 |
| TodoMVC Lines | ~60 | ~120 | ~90 |
| Learning Curve | Gentle | Moderate | Gentle-Moderate |
| Ecosystem Size | Growing | Largest | Large |
| TypeScript | First-class | First-class | First-class |
最佳实践
- 使用响应式声明($:)来计算值,而非手动更新
- 保持组件小而聚焦于单一职责
- 使用 store 进行跨组件状态管理,使用 context 进行子树状态管理
- 利用 SvelteKit 表单 actions 实现渐进增强
- 谨慎使用过渡以避免晕动症问题
- 在 Svelte 5 中优先使用 $derived 而非 $effect 来计算值
- 使用 load 函数获取数据,而非 onMount
- 使用 export const prerender = true 为静态内容启用预渲染
常见问题
Svelte 比 React 好吗?
与 React 相比,Svelte 提供更小的包体积、更快的运行时性能和更简单的语法。但 React 拥有更大的生态系统和更多的就业机会。Svelte 非常适合性能关键型应用和重视开发体验的团队。
Svelte 和 SvelteKit 有什么区别?
Svelte 是组件框架和编译器。SvelteKit 是构建在 Svelte 之上的全栈应用框架,提供路由、SSR、API 路由、表单 actions 和部署适配器。SvelteKit 之于 Svelte 就像 Next.js 之于 React。
Svelte 没有虚拟 DOM 如何实现响应式?
Svelte 在构建时将组件编译为命令式 JavaScript。编译器分析代码,识别响应式依赖,并生成精确的 DOM 更新。当响应式变量改变时,只有依赖它的特定 DOM 节点会被直接更新。
什么是 Svelte 5 runes?
Runes 是 Svelte 5 引入的编译器级原语:$state 用于响应式状态,$derived 用于计算值,$effect 用于副作用,$props 用于组件 props。它们提供了在任何地方都可用的细粒度响应式。
可以在 Svelte 中使用 TypeScript 吗?
可以,Svelte 对 TypeScript 有一等支持。在 .svelte 文件的 script 标签中添加 lang="ts" 即可。SvelteKit 项目默认使用 TypeScript 脚手架。
Svelte store 与 Redux 或 Zustand 相比如何?
Svelte store 比 Redux 简单得多。$ 自动订阅语法消除了样板代码。对于大多数 Svelte 应用,内置 store 就足够了。
SvelteKit 可以用于生产环境吗?
可以,SvelteKit 于 2022 年 12 月达到 1.0 版本。Apple、Spotify、纽约时报和宜家等公司都在使用。
如何部署 SvelteKit 应用?
SvelteKit 使用适配器进行部署。adapter-auto 自动检测平台。有专门的适配器用于 Vercel、Netlify、Cloudflare Pages、Node.js 服务器和静态站点生成。