Svelte 5引入了全新的Runes响应式系统,替代了Svelte 4中基于编译器魔法注释的响应式实现。Runes是一组以$开头的特殊函数,通过编译时转换实现细粒度响应式追踪。相比React的虚拟DOM diff和Vue的Proxy代理,Svelte的编译优化在构建阶段就生成了精确的DOM更新指令,运行时无需框架运行库开销。
Runes响应式API与状态声明
Svelte 5提供了$state、$derived、$effect三个核心Runes,分别用于声明响应式状态、派生值和副作用。与Svelte 4的let声明自动响应式不同,Runes要求显式标记响应式变量:
<script>
// Svelte 5 Runes 语法
let count = $state(0);
let doubled = $derived(count * 2);
let quadrupled = $derived(doubled * 2);
function increment() {
count++;
}
// 副作用:当count变化时自动执行
$effect(() => {
console.log(`count is ${count}, doubled is ${doubled}`);
});
</script>
<button onclick={increment}>
Count: {count}
</button>
<p>Doubled: {doubled}</p>
<p>Quadrupled: {quadrupled}</p>
$state声明可变状态,返回值的修改会触发依赖该状态的所有$derived和$effect重新执行。$derived声明计算属性,仅在其依赖的状态变化时重新计算,具有惰性求值和缓存特性。$effect在依赖变化时执行副作用,适用于日志记录、DOM操作、订阅外部数据源等场景。
深层响应式与$state.proxy原理
Svelte 5的$state对对象和数组自动创建深层响应式代理。修改对象的嵌套属性或数组的元素都会触发响应式更新,无需像React那样展开数组或使用immutable模式:
<script>
let todos = $state([
{ text: 'Learn Svelte 5', done: false },
{ text: 'Build an app', done: false }
]);
function toggleTodo(index) {
// 直接修改数组元素的属性,自动触发响应式
todos[index].done = !todos[index].done;
}
function addTodo(text) {
// 直接push,自动触发响应式
todos.push({ text, done: false });
}
let remaining = $derived(
todos.filter(t => !t.done).length
);
let total = $derived(todos.length);
</script>
<!-- 列表渲染 -->
{#each todos as todo, i}
<li>
<input type="checkbox"
checked={todo.done}
onchange={() => toggleTodo(i)} />
<span class:done={todo.done}>{todo.text}</span>
</li>
{/each}
<p>剩余: {remaining} / 总计: {total}</p>
Svelte 5通过Proxy实现深层响应式追踪。编译器将$state包装的对象转换为Proxy,对属性读取记录依赖关系,对属性赋值触发更新。这一过程对开发者透明,使用方式与普通对象无异,但响应式追踪是自动且精确的。
组件Props与$props Rune
Svelte 5统一了Props声明方式,使用$props Rune替代了Svelte 4的export let语法,支持默认值、解构和rest属性:
<script>
// Svelte 5 Props 声明
let {
title, // 必需属性
count = 0, // 带默认值
items = [], // 带默认值
onSave, // 回调函数
...rest // 其余属性
} = $props();
</script>
<h1>{title}</h1>
<p>Count: {count}</p>
<ul>
{#each items as item}
<li>{item}</li>
{/each}
</ul>
<button onclick={onSave}>Save</button>
<!-- rest属性透传 -->
<div {...rest}>
<slot />
</div>
编译优化机制与包体积对比
Svelte的核心优势在于编译时优化。编译器分析组件的响应式依赖关系,生成精确的DOM更新代码,而非通用的diff算法。以下是一个编译前后的对比示例:
// 编译前 - Svelte组件
<script>
let name = $state('World');
</script>
<h1>Hello {name}!</h1>
// 编译后 - JavaScript(简化版)
// 编译器知道只有 <h1> 的文本节点依赖name
// 当name变化时,直接更新该文本节点,无需diff
function instance($$anchor, $$props) {
let name = $.state('World');
// 精确的DOM更新指令
$.template(() => {
let h1 = $.element('h1');
let text = $.text(`Hello ${$.get(name)}!`);
$.append(h1, text);
$.append($$anchor, h1);
});
// 当name变化时,只更新text节点
$.effect(() => {
$.set_text(text, `Hello ${$.get(name)}!`);
});
return { name };
}
这种编译策略使得Svelte应用的运行时包体积极小。以下是主流框架的基线包体积对比:
# Hello World 应用的框架运行时体积
# Svelte 5: ~ 2KB (gzipped, 无运行时框架依赖)
# Vue 3: ~ 34KB (gzipped, 含响应式系统+运行时)
# React 18: ~ 42KB (gzipped, react-dom + react)
# Solid.js: ~ 7KB (gzipped, 类似Svelte的编译优化)
# TodoMVC 应用完整体积(含业务代码,gzipped)
# Svelte 5: ~ 10KB
# Vue 3: ~ 45KB
# React 18: ~ 55KB
SvelteKit全栈路由与数据加载
SvelteKit是Svelte的官方全栈框架,提供文件系统路由、SSR/SSG、数据加载等能力。SvelteKit 2配合Svelte 5的Runes系统,支持在load函数中使用响应式状态:
// src/routes/posts/[slug]/+page.server.js
import { db } from '$lib/server/database';
export async function load({ params }) {
const post = await db.post.findUnique({
where: { slug: params.slug }
});
if (!post) {
throw error(404, 'Post not found');
}
return { post };
}
// src/routes/posts/[slug]/+page.svelte
<script>
let { data } = $props();
let { post } = data;
// 使用Runes管理本地状态
let comment = $state('');
let comments = $state(post.comments);
async function submitComment() {
const res = await fetch(`/api/posts/${post.slug}/comments`, {
method: 'POST',
body: JSON.stringify({ text: comment })
});
const newComment = await res.json();
comments.push(newComment);
comment = '';
}
</script>
<article>
<h1>{post.title}</h1>
{@html post.content}
</article>
<section>
<h2>Comments ({comments.length})</h2>
{#each comments as c}
<p>{c.text}</p>
{/each}
<input bind:value={comment} />
<button onclick={submitComment}>Submit</button>
</section>
过渡动画与Svelte内置指令
Svelte内置了过渡动画系统,通过编译时指令实现声明式动画,无需引入额外动画库:
<script>
import { fade, fly, slide } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
let visible = $state(true);
let items = $state(['A', 'B', 'C']);
</script>
<button onclick={() => visible = !visible}>
Toggle
</button>
{#if visible}
<p transition:fade={{ duration: 300 }}>
Fades in and out
</p>
{/if}
<!-- 列表项动画 -->
{#each items as item, i (item)}
<li
in:fly={{ x: 200, duration: 400, easing: quintOut }}
out:slide={{ duration: 200 }}
>
{item}
</li>
{/each}
transition指令在编译时转换为对应的动画函数调用,运行时仅包含实际使用的动画代码,未使用的动画实现会被Tree-shaking移除。这种设计使得Svelte应用可以享受丰富的动画能力,同时不增加不必要的包体积。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/svelte5-kuang-jia-runes-xiang-ying-shi-xi-tong-yu-bian-yi/