Svelte 4 迁移到 Svelte 5
本教程共 50 篇 · 第 42 篇 · 更新于 2026-08-05 · 约 5 分钟阅读
本节目标:学会用迁移脚本和手动方式把 Svelte 4 代码迁移到 Svelte 5 Runes 语法,掌握所有核心 API 的旧新对照。
渐进式迁移
Svelte 5 仍然支持 Svelte 4 的旧语法。你不需要一次性全部迁移,可以新旧混用,逐个组件改造。这让迁移风险大大降低。
迁移脚本
官方提供了自动迁移脚本:
npx sv migrate svelte-5
它会自动完成以下工作:
- 升级
package.json中的核心依赖版本 let→$stateon:click→onclick<slot />→{@render children?.()}<div slot="x">→{#snippet x()}<div>{/snippet}new Component(...)→mount(Component, ...)
Tip在 VS Code 中可以右键单个组件,选择 “Migrate Component to Svelte 5 Syntax” 来逐个迁移。在官方 Playground 上也有 “Migrate” 按钮。
脚本不能完成所有迁移,有些需要手动处理。下面是核心对照表。
旧写法对照表
| 用途 | Svelte 4 | Svelte 5 |
|---|---|---|
| 组件状态 | let count = 0 | let count = $state(0) |
| 计算值 | $: double = count * 2 | let double = $derived(count * 2) |
| 副作用 | $: { ... } | $effect(() => { ... }) |
| Props | export let foo | let { foo } = $props() |
| 双向绑定 | export let value; bind:value | let { value = $bindable() } = $props() |
| 事件 | on:click={fn} | onclick={fn} |
| 组件事件 | createEventDispatcher | 回调 props |
| 默认插槽 | <slot /> | {@render children?.()} |
| 具名插槽 | <slot name="header" /> | {@render header()} |
| 创建组件 | new App({ target }) | mount(App, { target }) |
| 销毁 | app.$destroy() | unmount(app) |
| 更新 props | app.$set({ x: 1 }) | props.x = 1(props 用 $state) |
| 所有 props | $$props | let props = $props() |
| 剩余 props | $$restProps | let { foo, ...rest } = $props() |
let → $state
Svelte 4 中 let 声明在组件顶层自动是响应式的。Svelte 5 需要显式用 $state:
<!-- Svelte 4 -->
<script>
let count = 0;
</script>
<!-- Svelte 5 -->
<script>
let count = $state(0);
</script>
Svelte 4 中数组用 .push() 不会触发更新,需要重新赋值。Svelte 5 中 $state 是深度响应式的,直接 .push() 就行:
<!-- Svelte 4 -->
<script>
let items = [];
function add() {
items.push('new');
items = items; // 需要重新赋值触发更新
}
</script>
<!-- Svelte 5 -->
<script>
let items = $state([]);
function add() {
items.push('new'); // 直接生效,不需要重新赋值
}
</script>
$: → $derived / $effect
Svelte 4 的 $: 既用于计算值也用于副作用。Svelte 5 拆成了两个:
<!-- Svelte 4 -->
<script>
let count = 0;
$: double = count * 2; // 计算值
$: { // 副作用
if (count > 5) alert('太多!');
}
</script>
<!-- Svelte 5 -->
<script>
let count = $state(0);
let double = $derived(count * 2); // 计算值
$effect(() => { // 副作用
if (count > 5) alert('太多!');
});
</script>
Note90% 的情况应该用
$derived。$effect是最后手段。
| 情况 | 用什么 |
|---|---|
| 纯计算 → 新值 | $derived |
| 有副作用(DOM 操作、日志、外部同步) | $effect |
export let → $props
Svelte 4 的 export let 改为 $props() 解构:
<!-- Svelte 4 -->
<script>
export let optional = '默认值';
export let required;
export { klass as class };
</script>
<!-- Svelte 5 -->
<script>
let { optional = '默认值', required, class: klass } = $props();
</script>
$$props 和 $$restProps 用标准解构语法替代:
<!-- Svelte 4 -->
<button class={klass} {...$$restProps}>...</button>
<!-- Svelte 5 -->
<script>
let { class: klass, ...rest } = $props();
</script>
<button class={klass} {...rest}>...</button>
on:click → onclick
事件指令改成属性形式,去掉冒号:
<!-- Svelte 4 -->
<button on:click={handleClick}>点击</button>
<!-- Svelte 5 -->
<button onclick={handleClick}>点击</button>
属性简写也支持:
<button {onclick}>点击</button>
事件修饰符
Svelte 4 的 on:click|preventDefault|once 在 Svelte 5 中不支持。改为在处理器里手动处理:
<!-- Svelte 4 -->
<button on:click|preventDefault={handler}>提交</button>
<!-- Svelte 5 -->
<script>
function handler(e) {
e.preventDefault();
// 你的逻辑
}
</script>
<button onclick={handler}>提交</button>
capture 修饰符改为属性名后缀:
<button onclickcapture={handler}>...</button>
createEventDispatcher → 回调 props
这是改动最大的部分。Svelte 4 用 createEventDispatcher 发送事件,Svelte 5 改用回调 props:
<!-- Pump.svelte (Svelte 4) -->
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
</script>
<button on:click={() => dispatch('inflate', 5)}>充气</button>
<!-- Pump.svelte (Svelte 5) -->
<script>
let { inflate } = $props();
</script>
<button onclick={() => inflate(5)}>充气</button>
父组件那边:
<!-- Svelte 4 -->
<Pump on:inflate={(e) => size += e.detail} />
<!-- Svelte 5 -->
<Pump inflate={(power) => size += power} />
Tip回调 props 比事件分发更直接。不需要
import、不需要dispatch、不需要.detail,就是普通函数调用。
slot → snippet
<slot /> 改为 children prop 加 {@render}:
<!-- Svelte 4 -->
<slot />
<!-- Svelte 5 -->
<script>
let { children } = $props();
</script>
{@render children?.()}
具名插槽改为命名的 snippet prop:
<!-- Svelte 4 -->
<slot name="header" />
<slot name="footer" />
<!-- Svelte 5 -->
<script>
let { header, footer } = $props();
</script>
{@render header?.()}
{@render footer?.()}
迁移脚本的手动清理
脚本会把无法自动判断的 $: 语句转成 svelte/legacy 的 run 函数。你需要手动改成 $effect 或 $derived:
<!-- 脚本输出 -->
<script>
import { run } from 'svelte/legacy';
run(() => {
// 副作用代码
});
</script>
<!-- 手动改为 -->
<script>
$effect(() => {
// 副作用代码
});
</script>
事件修饰符会被转成 svelte/legacy 的包装函数,同样需要手动清理:
<!-- 脚本输出 -->
<script>
import { preventDefault } from 'svelte/legacy';
</script>
<button onclick={preventDefault(handler)}>...</button>
<!-- 手动改为 -->
<script>
function handler(e) {
e.preventDefault();
// 你的逻辑
}
</script>
<button onclick={handler}>...</button>
本节回顾
npx sv migrate svelte-5自动迁移大部分代码,但需要手动清理let→$state,数组可直接.push()触发更新$:计算值 →$derived,副作用 →$effectexport let→$props()解构,$$restProps→ rest 解构on:click→onclick,事件修饰符改为手动处理createEventDispatcher→ 回调 props,更简单直接<slot>→childrenprop +{@render children()}- 迁移脚本输出的
svelte/legacy函数需手动改为$effect或内联处理