Svelte 5引入了全新的Runes响应式系统,取代了Svelte 4基于编译器魔术的响应式方案。Runes是一组以$开头的特殊函数,由Svelte编译器在编译阶段识别并转换。本文深入解析Runes的工作原理、核心API使用方式与编译时优化机制。
Svelte 5 Runes响应式系统与$state/$derived机制
Runes是Svelte 5的核心特性,提供了显式的响应式声明方式。与Vue 3的Composition API类似但更轻量,Runes不需要import即可使用,由编译器在编译阶段处理。
$state是最基础的Rune,用于声明响应式状态。当$state变量的值发生变化时,所有依赖该变量的DOM更新和计算会自动触发。
<script>
let count = $state(0);
let user = $state({ name: 'Alice', age: 28 });
let items = $state([]);
function increment() {
count++;
}
function addItem() {
items.push({ id: crypto.randomUUID(), label: `Item ${items.length}` });
}
</script>
<button onclick={increment}>Count: {count}</button>
<button onclick={addItem}>Add Item ({items.length})</button>
<ul>
{#each items as item (item.id)}
<li>{item.label}</li>
{/each}
</ul>
Svelte 5的$state对数组push、splice等变异方法和对象属性赋值都能自动追踪,不需要额外的 reactive() 包装或展开运算符。这比Vue 3的ref需要.value访问更直观。
$derived用于声明派生状态,当依赖的$state变化时自动重新计算。$derived有两种写法:表达式形式和函数形式。
<script>
let price = $state(100);
let quantity = $state(3);
let discount = $state(0.1);
// 表达式形式
let total = $derived(price * quantity * (1 - discount));
// 函数形式(支持复杂逻辑)
let summary = $derived(() => {
const subtotal = price * quantity;
const discounted = subtotal * (1 - discount);
return {
subtotal: subtotal.toFixed(2),
discount: (subtotal * discount).toFixed(2),
total: discounted.toFixed(2)
};
});
</script>
$effect副作用管理与生命周期控制
$effect用于声明副作用,在依赖的响应式状态变化后执行。与React的useEffect不同,$effect的依赖是自动追踪的,不需要手动声明依赖数组。
<script>
let theme = $state('light');
let apiKey = $state('');
// 自动追踪theme变化
$effect(() => {
document.documentElement.setAttribute('data-theme', theme);
});
// 带清理函数
$effect(() => {
const controller = new AbortController();
fetch(`/api/data?key=${apiKey}`, { signal: controller.signal })
.then(r => r.json())
.then(data => { /* ... */ });
return () => controller.abort();
});
// $effect.pre在DOM更新前执行
$effect.pre(() => {
console.log('Theme will change to:', theme);
});
</script>
$effect的清理函数在上一次effect重新执行前调用,用于清理定时器、取消请求等。$effect.pre在DOM更新前执行,适合需要在视图更新前做准备工作的场景。避免在$effect中直接修改$state造成无限循环,如需基于effect结果更新状态,应使用$derived。
$props与组件接口设计
Svelte 5使用$props Rune声明组件属性,替代了Svelte 4的export let语法。$props支持解构默认值、剩余属性收集和绑定修饰符。
<script>
// 基础props声明
let { title, count = 0, onSubmit } = $props();
// 带类型注解(TypeScript)
interface Props {
title: string;
items: string[];
onRemove?: (index: number) => void;
children?: import('svelte').Snippet;
}
let { title, items, onRemove, children }: Props = $props();
// bindable属性(父组件可双向绑定)
let { value = $bindable('') } = $props();
// 剩余属性收集
let { class: className, ...restProps } = $props();
</script>
编译时优化与虚拟DOM消除机制
Svelte的核心优势在于编译时优化。Svelte编译器在构建阶段分析组件结构,生成直接操作DOM的命令式代码,运行时不需要虚拟DOM diff算法。这使得Svelte应用的包体积更小、运行性能更高。
Svelte 5的编译器对Runes的处理方式与Svelte 4对export let的处理有本质区别。Runes是真实的函数调用(在编译后被替换),而非编译器魔术。这意味着Runes代码在TypeScript类型检查和IDE智能提示中能被正确识别,而Svelte 4的export let需要特殊工具链支持。
编译器对$state的优化策略:基本类型(string、number、boolean)编译为信号(signal)创建和get/set拦截。对象和数组使用Proxy包装实现深度响应式。$derived编译为依赖追踪的计算函数,仅在依赖变化时重新计算,避免不必要的重复计算。
// Svelte 5 编译前
let count = $state(0);
let doubled = $derived(count * 2);
// Svelte 5 编译后(简化示意)
const count = $.source(0);
const doubled = $.derived(() => $.get(count) * 2);
// 模板中的访问被替换为 $.get() 调用
// <p>{count}</p> 编译为
$.template(() => `<p>${$.get(count)}</p>`, [count]);
Snippet与组件组合模式
Svelte 5引入了Snippet替代slot机制,提供更强的类型安全和组合能力。Snippet本质上是可复用的渲染片段,通过render方法调用。
<script>
let { data } = $props();
// 声明Snippet
snippet renderItem(item) {
<div class="card">
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
}
</script>
<!-- 传给子组件 -->
<List items={data}>
{#snippet row(item)}
<span class="row">{item.name}</span>
{/snippet}
</List>
<!-- 条件渲染Snippet -->
{#if data.length > 0}
{@render renderItem(data[0])}
{/if}
从Svelte 4迁移策略与兼容性处理
Svelte 5对Svelte 4保持向后兼容,同一项目可混合使用两种语法。迁移建议分阶段进行:先升级svelte包到5.x版本,在兼容模式下运行现有组件,逐步将export let改为$props、$:改为$derived、store用法保持不变。使用svelte-migrate工具自动完成大部分机械性替换。
# 执行自动迁移
npx svelte-migrate@latest runes
迁移后需关注$state对对象和数组的行为变化:Svelte 4的reactive声明在数组赋值时触发更新,Svelte 5的$state在数组变异方法调用时也会触发,无需额外处理。但深层次嵌套对象的响应式追踪行为可能不同,需逐组件验证。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/svelte5runes-xiang-ying-shi-xi-tong-yuan-li-yu-bian-yi-shi/