Svelte 5迁移指南
Svelte 5 采用了全面改进的语法和响应性系统。虽然开始时可能看起来有所不同,但您很快会注意到许多相似之处。本指南详细介绍了这些变化,并向您展示如何升级。同时,我们还提供了关于我们为什么做出这些改变的信息。
您不必立即迁移至新语法 —— Svelte 5 仍然支持旧的 Svelte 4 语法,您可以将使用新语法的组件与使用旧语法的组件混合使用。我们预计很多人可以通过仅修改几行代码就完成升级。还有一个 迁移脚本 可以帮助您自动完成许多步骤。
响应性语法变化
Svelte 5 的核心是新的符文 API。符文基本上是编译器指令,告诉 Svelte 有关响应性的信息。在语法上,符文是以美元符号开头的函数。
let -> $state
在 Svelte 4 中,组件顶层的 let
声明是隐式响应式的。在 Svelte 5 中,事情变得更明确:当使用 $state
符文创建变量时,该变量是响应式的。让我们通过将计数器包装在 $state
中来迁移到符文模式:
<script>
let count = $state(0);
</script>
其他方面没有变化。count
仍然是数字本身,您可以直接读写它,没有 .value
或 getCount()
这样的包装器。
我们为什么这样做
let
在顶层隐式声明响应式工作良好,但这意味着响应性受到限制——在其他地方的let
声明都不是响应式的。这迫使您在重构代码以便复用时不得不使用 store。这意味着您必须学习一个完全不同的响应模型,结果通常并不那么好用。由于 Svelte 5 中的响应性更明确,您可以在组件顶层之外继续使用相同的 API。请前往 教程 了解更多信息。
$: -> $derived/$effect
在 Svelte 4 中,组件顶层的 $:
语句可用于声明派生,即完全通过其他状态的计算来定义的状态。在 Svelte 5 中,可以使用 $derived
符文实现这一点:
<script>
let count = $state(0);
$: const double = $derived(count * 2);
</script>
与 $state
一样,其他方面没有变化。double
仍然是数字本身,您可以直接读取它,而不需要像 .value
或 getDouble()
这样的包装器。
$:
语句还可以用于创建副作用。在 Svelte 5 中,可以使用 $effect
符文实现这一点:
<script>
let count = $state(0);
$:$effect(() => {
if (count > 5) {
alert('Count is too high!');
}
});
</script>
我们为什么这样做
$:
是一个很好的简写,容易上手:您可以在大多数代码前加上$:
它就能以某种方式工作。这种直观性也是它的缺点,因为您的代码变得更复杂时,它并不那么好理解。代码的意图是创建一个派生,还是创建一个副作用?使用$derived
和$effect
,您需要进行更多的前期决策(剧透:90% 的时候您想要$derived
),但将来您和团队中的其他开发人员会更容易理解。还有一些难以发现的陷阱:
$:
仅在渲染之前直接更新,这意味着在重新渲染之间你可能会读取到过时的值$:
仅在每个 tick 中运行一次,这意味着语句的运行频率可能低于你的预期$:
依赖关系是通过对依赖项的静态分析确定的。这在大多数情况下有效,但在重构过程中可能会以微妙的方式出错,例如依赖项被移动到一个函数中,从而不再可见$:
语句的顺序也是通过对依赖项的静态分析来确定的。在某些情况下可能会出现平局,导致排序错误,需要手动干预。在重构代码时,顺序也可能会出错,某些依赖项因此不再可见。最后,它对 TypeScript 不友好(我们的编辑器工具需要跳过一些环节才能使其对 TypeScript 有效),这是使 Svelte 的响应模型真正通用的障碍。
$derived
和$effect
解决了所有这些问题:
- 始终返回最新值
- 根据需要运行以保持稳定
- 在运行时确定依赖关系,因此对重构免疫
- 根据需要执行依赖关系,因此免受排序问题的影响
- 对于 TypeScript 友好
export let -> $props
在 Svelte 4 中,组件的属性是通过 export let
声明的。每个属性都是一个声明。在 Svelte 5 中,所有属性都是通过 $props
符文声明的,通过解构:
<script>
export let optional = 'unset';
export let required;
let { optional = 'unset', required } = $props();
</script>
在某些情况下,声明属性变得不如有几个 export let
声明那样简单:
- 您想重命名属性,例如因为名称是保留标识符(例如
class
) - 您不知道预期还有哪些其他属性
- 您想将每个属性转发到另一个组件
在 Svelte 4 中,所有这些情况都需要特殊语法:
- 重命名:
export { klass as class}
- 其他属性:
$$restProps
- 所有属性:
$$props
在 Svelte 5 中,$props
符文使这变得简单,无需任何额外的 Svelte 特定语法:
- 重命名:使用属性重命名
let { class: klass } = $props();
- 其他属性:使用展开语法
let { foo, bar, ...rest } = $props();
- 所有属性:不要解构
let props = $props();
<script>
let klass = '';
export { klass as class};
let { class: klass, ...rest } = $props();
</script>
<button class={klass} {...$$restPropsrest}>点击我</button>
我们为什么这样做
export let
是一个颇具争议的 API 决策,围绕您是否应该考虑属性被export
或import
存在了很多争论。$props
没有这种特性。这也与其他符文保持一致,总体思路简化为“在 Svelte 中,所有与响应性有关的都是符文”。
export let
还存在许多局限性,需要额外的 API 去解决,如上所示。$props
将这些统一为一个语法概念,严重依赖常规的 JavaScript 解构语法
事件变化
在 Svelte 5 中,事件处理程序进行了改头换面。在 Svelte 4 中,我们使用 on:
指令将事件监听器附加到元素上,而在 Svelte 5 中,它们像其他属性一样(换句话说 —— 去掉冒号):
<script>
let count = $state(0);
</script>
<button on:click={() => count++}>
点击次数:{count}
</button>
由于它们只是属性,您可以使用正常的简写语法...
<script>
let count = $state(0);
function onclick() {
count++;
}
</script>
<button {onclick}>
点击次数:{count}
</button>
...尽管在使用命名事件处理函数时,通常最好使用更具描述性的名称。
组件事件
在 Svelte 4 中,组件可以使用 createEventDispatcher
创建一个调度器来发出事件。
该函数在 Svelte 5 中已弃用。相反,组件应接受 回调属性 —— 这意味着您可以将函数作为属性传递给这些组件:
<script>
import Pump from './Pump.svelte';
let size = $state(15);
let burst = $state(false);
function reset() {
size = 15;
burst = false;
}
</script>
<Pump
on:inflate={(power) => {
size += power.detail;
if (size > 75) burst = true;
}}
on:deflate={(power) => {
if (size > 0) size -= power.detail;
}}
/>
{#if burst}
<button onclick={reset}>新气球</button>
<span class="boom">💥</span>
{:else}
<span class="balloon" style="scale: {0.01 * size}">
🎈
</span>
{/if}
<script lang="ts">
import Pump from './Pump.svelte';
let size = $state(15);
let burst = $state(false);
function reset() {
size = 15;
burst = false;
}
</script>
<Pump
on:inflate={(power) => {
size += power.detail;
if (size > 75) burst = true;
}}
on:deflate={(power) => {
if (size > 0) size -= power.detail;
}}
/>
{#if burst}
<button onclick={reset}>新气球</button>
<span class="boom">💥</span>
{:else}
<span class="balloon" style="scale: {0.01 * size}">
🎈
</span>
{/if}
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
let { inflate, deflate } = $props();
let power = $state(5);
</script>
<button onclick={() => dispatch('inflate', power)inflate(power)}>
充气
</button>
<button onclick={() => dispatch('deflate', power)deflate(power)}>
放气
</button>
<button onclick={() => power--}>-</button>
泵的气压:{power}
<button onclick={() => power++}>+</button>
<script lang="ts">
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
let { inflate, deflate } = $props();
let power = $state(5);
</script>
<button onclick={() => dispatch('inflate', power)inflate(power)}>
充气
</button>
<button onclick={() => dispatch('deflate', power)deflate(power)}>
放气
</button>
<button onclick={() => power--}>-</button>
泵的气压:{power}
<button onclick={() => power++}>+</button>
事件冒泡
组件应该接受一个 onclick
回调属性,而不是通过 <button on:click>
将事件从元素“转发”到组件:
<script>
let { onclick } = $props();
</script>
<button on:click {onclick}>
点击我
</button>
请注意,这也意味着您可以将事件处理程序与其他属性一起“展开”到元素上,而不必繁琐地单独转发每个事件:
<script>
let props = $props();
</script>
<button {...$$props} on:click on:keydown on:all_the_other_stuff {...props}>
点击我
</button>
事件修饰符
在 Svelte 4 中,您可以向事件处理程序添加事件修饰符:
<button on:click|once|preventDefault={handler}>...</button>
修饰符特定于 on:
,因此不适用于现代事件处理程序。在处理程序内部添加 event.preventDefault()
等内容是更可取的,因为所有逻辑都集中在一个地方,而不是拆分在处理程序和修饰符之间。
由于事件处理程序只是函数,您可以根据需要创建自己的封装:
<script>
function once(fn) {
return function (event) {
if (fn) fn.call(this, event);
fn = null;
};
}
function preventDefault(fn) {
return function (event) {
event.preventDefault();
fn.call(this, event);
};
}
</script>
<button onclick={once(preventDefault(handler))}>...</button>
有三个修饰符——capture
、passive
和 nonpassive
—— 不能被表示为包装函数,因为它们需要在事件处理程序绑定时应用,而不是在运行时应用。
对于 capture
,我们将修饰符添加到事件名称中:
<button onclickcapture={...}>...</button>
更改事件处理程序的 passive
选项并不是轻而易举的事情。如果您有此用例——您可能没有!——那么您需要使用一个 action 来自己应用事件处理程序。
多个事件处理程序
在 Svelte 4 中,这样做是可以的:
<button on:click={one} on:click={two}>...</button>
元素上的重复特性/属性 —— 现在包括事件处理程序 —— 是不允许的。相反,请改为这样做:
<button
onclick={(e) => {
one(e);
two(e);
}}
>
...
</button>
在展开属性时,本地事件处理程序必须在展开之后,否则可能会被覆盖:
<button
{...props}
onclick={(e) => {
doStuff(e);
props.onclick?.(e);
}}
>
...
</button>
我们为什么这样做
createEventDispatcher
一直有点模板化:
- 导入函数
- 调用该函数以获取调度函数
- 使用字符串和可能的有效负载调用该调度函数
- 通过
.detail
属性在另一端检索该有效负载,因为事件本身始终是CustomEvent
一直可以使用组件回调属性,但由于您必须使用
on:
监听 DOM 事件,因此出于语法一致性,使用createEventDispatcher
处理组件事件是有意义的。现在我们有了事件属性(onclick
),情况正好相反:回调属性现在是更合理的选择。放弃事件修饰符无疑是对那些喜欢事件修饰符简写语法的人的一种倒退。考虑到它们并不常用,我们用更小的表面积换取了更明确性。修饰符也不一致,因为它们中的大多数只能用于 DOM 元素。
同一事件的多个监听器也不再可能,但这本身就是一种反模式,因为它妨碍了可读性:如果有很多属性,则更难发现有两个处理程序,除非它们紧挨在一起。它还暗示这两个处理程序是独立的,而实际上,如果
one
内部包含event.stopImmediatePropagation()
,会阻止two
被调用。通过弃用
createEventDispatcher
和on:
指令,改为使用回调属性和普通元素属性,我们:
- 降低了 Svelte 的学习曲线
- 消除了样板代码,特别是在
createEventDispatcher
周围- 消除了为可能没有监听者的事件创建
CustomEvent
对象的开销- 增加了展开事件处理程序的能力
- 增加了了解哪些事件处理程序被提供给组件的能力
- 增加了表达给定事件处理程序是必需的还是可选的能力
- 提高了类型安全性(之前,Svelte 实际上无法保证组件不发出特定事件)
代码片段而非插槽
在 Svelte 4 中,可以使用插槽将内容传递给组件。Svelte 5 用更强大和灵活的代码片段替换了它们,因此插槽在 Svelte 5 中被弃用。
不过,它们仍然可以继续使用,您可以在组件中混合使用代码片段和插槽。
在使用自定义元素时,您仍然应该像以前一样使用 <slot />
。在未来的版本中,当 Svelte 移除其内部版本的插槽时,它将保持这些插槽不变,即输出一个常规的 DOM 标签,而不是进行转换。
默认内容
在 Svelte 4 中,传递 UI 给子组件的最简单方法是使用一个 <slot />
。在 Svelte 5 中,改为使用 children
属性,然后通过 {@render children()}
显示:
<script>
let { children } = $props();
</script>
<slot />
{@render children?.()}
多个内容占位符
如果您想要多个 UI 占位符,您必须使用命名插槽。在 Svelte 5 中,改为使用 props,随意命名它们,并 {@render ...}
它们:
<script>
let { header, main, footer } = $props();
</script>
<header>
<slot name="header" />
{@render header()}
</header>
<main>
<slot name="main" />
{@render main()}
</main>
<footer>
<slot name="footer" />
{@render footer()}
</footer>
向上传递数据
在 Svelte 4 中,您将数据传递给 <slot />
,然后在父组件中使用 let:
检索它。在 Svelte 5 中,代码片段承担了这一责任:
<script>
import List from './List.svelte';
</script>
<List items={['one', 'two', 'three']} let:item>
{#snippet item(text)}
<span>{text}</span>
{/snippet}
<span slot="empty">尚无条目</span>
{#snippet empty()}
<span>尚无条目</span>
{/snippet}
</List>
<script lang="ts">
import List from './List.svelte';
</script>
<List items={['one', 'two', 'three']} let:item>
{#snippet item(text)}
<span>{text}</span>
{/snippet}
<span slot="empty">尚无条目</span>
{#snippet empty()}
<span>尚无条目</span>
{/snippet}
</List>
<script>
let { items, item, empty } = $props();
</script>
{#if items.length}
<ul>
{#each items as entry}
<li>
<slot item={entry} />
{@render item(entry)}
</li>
{/each}
</ul>
{:else}
<slot name="empty" />
{@render empty?.()}
{/if}
<script lang="ts">
let { items, item, empty } = $props();
</script>
{#if items.length}
<ul>
{#each items as entry}
<li>
<slot item={entry} />
{@render item(entry)}
</li>
{/each}
</ul>
{:else}
<slot name="empty" />
{@render empty?.()}
{/if}
我们为什么这样做
插槽易于上手,但随着用例的复杂性增加,语法越发复杂和令人困惑:
let:
语法让许多人感到困惑,因为它创建了一个变量,而所有其他:
指令则是接收一个变量- 用
let:
声明的变量的作用域并不清晰。在上面的例子中,您可能会认为可以在empty
插槽中使用item
插槽属性,但事实并非如此- 命名插槽必须使用
slot
属性应用于元素。有时您不希望创建一个元素,因此我们不得不添加<svelte:fragment>
API- 命名插槽也可以应用于组件,这改变了
let:
指令可用范围的语义(即使在今天,我们的维护者也常常不知道它的工作原理)代码片段通过更具可读性和清晰性解决了所有这些问题。同时,它们更加强大,因为它们允许您定义可以在 任何地方 渲染的 UI 部分,而不仅仅是将其作为 props 传递给组件。
迁移脚本
到目前为止,您应该对之前/之后的情况以及旧语法与新语法的关系有了相当不错的理解。您可能也意识到了,很多迁移都是相当技术性和重复的,您并不想手动完成这些事情。
我们也是这样认为的,这就是为什么我们提供了迁移脚本,用于自动完成大部分迁移。您可以使用 npx sv migrate svelte-5
升级您的项目。这将执行以下操作:
- 更新您的
package.json
中的核心依赖项 - 迁移到符文(
let
->$state
等) - 将 DOM 元素的事件属性迁移为事件属性(
on:click
->onclick
) - 将插槽创建迁移为渲染标签(
<slot />
->{@render children()}
) - 将插槽用法迁移至片段(
<div slot="x">...</div>
->{#snippet x()}<div>...</div>{/snippet}
) - 迁移明显的组件创建(
new Component(...)
->mount(Component, ...)
)
您还可以通过 VS Code 中的 Migrate Component to Svelte 5 Syntax
命令迁移单个组件,或在我们的 Playground 中通过 Migrate
按钮完成。
并非所有内容都可以自动迁移,一些迁移在之后需要手动清理。以下部分将更详细地描述这些内容。
run
您可能会看到迁移脚本将一些 $:
语句转换为从 svelte/legacy
导入的 run
函数。如果迁移脚本无法可靠地将语句迁移到 $derived
并得出结论这是一个副作用,就会发生这种情况。
在某些情况下,这可能是错误的,最好将其改为使用 $derived
。在其他情况下,这可能是正确的,但由于 $:
语句在服务端也会运行,而 $effect
不会,因此不能安全地转换它。于是,run
被用作权宜之计。run
模拟了 $:
的大多数特征,因为它在服务端上运行一次,并在客户端作为 $effect.pre
运行($effect.pre
在更改应用于 DOM 之前运行;您最有可能想要使用 $effect
代替)。
<script>
import { run } from 'svelte/legacy';
run(() => {
$effect(() => {
// 一些副作用代码
})
</script>
事件修饰符
事件修饰符不适用于事件属性(例如,您不能做 onclick|preventDefault={...}
)。因此,当将事件指令迁移到事件属性时,我们需要一个函数替代这些修饰符。这些从 svelte/legacy
中导入,帮助支持迁移,例如仅使用 event.preventDefault()
。
<script>
import { preventDefault } from 'svelte/legacy';
</script>
<button
onclick={preventDefault((event) => {
event.preventDefault();
// ...
})}
>
点击我
</button>
不会自动迁移的内容
迁移脚本不会转换 createEventDispatcher
。您需要手动调整这些部分。之所以这样做,是因为风险太大,可能会导致组件出现故障,而迁移脚本无法发现这一点。
迁移脚本不会转换 beforeUpdate/afterUpdate
。之所以这样做,是因为无法确定代码的实际意图。作为经验法则,您通常可以结合使用 $effect.pre
(在与 beforeUpdate
同时运行)和 tick
(从 svelte
导入,让您等到更改应用于 DOM,然后再进行一些工作)。
组件不再是类
在 Svelte 3 和 4 中,组件是类。在 Svelte 5 中,它们是函数,应该以不同方式实例化。如果您需要手动实例化组件,您应该使用 mount
或 hydrate
(从 svelte
导入)。如果您在使用 SvelteKit 时看到此错误,请先尝试更新到最新版本的 SvelteKit,该版本添加了对 Svelte 5 的支持。如果您在没有 SvelteKit 的情况下使用 Svelte,您可能会有一个 main.js
文件(或类似的文件),您需要进行调整:
import { function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: MountOptions<Props>): Exports
Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
mount } from 'svelte';
import type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte'
const app = new App({ target: document.getElementById("app") });
const const app: {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>
app = mount<Record<string, any>, {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<...>>(component: ComponentType<...> | Component<...>, options: MountOptions<...>): {
...;
} & Record<...>Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
const App: LegacyComponentType
App, { target: Document | Element | ShadowRoot
Target element where the component will be mounted.
var document: Document
document.Document.getElementById(elementId: string): HTMLElement | null
Returns a reference to the first object with the specified value of the ID attribute.
const app: {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>
app;mount
和 hydrate
具有完全相同的 API。不同之处在于 hydrate
会在其目标内提取 Svelte 的服务端渲染 HTML 并进行水合。两者都返回一个包含组件导出的对象以及可能的属性访问器(如果编译时使用 accessors: true
)。它们不包含您可能熟悉的类组件 API 中的 $on
、$set
和 $destroy
方法。这些是它的替代品:
对于 $on
,不要监听事件,而是通过 events
属性在选项参数中传递它们。
import { function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: MountOptions<Props>): Exports
Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
mount } from 'svelte';
import type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte'
const app = new App({ target: document.getElementById("app") });
app.$on('event', callback);
const const app: {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>
app = mount<Record<string, any>, {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<...>>(component: ComponentType<...> | Component<...>, options: MountOptions<...>): {
...;
} & Record<...>Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
const App: LegacyComponentType
App, { target: Document | Element | ShadowRoot
Target element where the component will be mounted.
var document: Document
document.Document.getElementById(elementId: string): HTMLElement | null
Returns a reference to the first object with the specified value of the ID attribute.
events?: Record<string, (e: any) => any> | undefined
Allows the specification of events.
event: any
event: callback } });请注意,使用
events
是不推荐的——请改为 使用回调
对于 $set
,请使用 $state
来创建一个响应式属性对象并进行操作。如果您在 .js
或 .ts
文件中执行此操作,请调整文件结尾包含 .svelte
,即 .svelte.js
或 .svelte.ts
。
import { function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: MountOptions<Props>): Exports
Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
mount } from 'svelte';
import type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte'
const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
app.$set({ foo: 'baz' });
const const props: {
foo: string;
}
props = function $state<{
foo: string;
}>(initial: {
foo: string;
}): {
foo: string;
} (+1 overload)
namespace $state$state({ foo: string
foo: 'bar' });
const const app: {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>app = mount<Record<string, any>, {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<...>>(component: ComponentType<...> | Component<...>, options: MountOptions<...>): {
...;
} & Record<...>Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
const App: LegacyComponentType
App, { target: Document | Element | ShadowRoot
Target element where the component will be mounted.
var document: Document
document.Document.getElementById(elementId: string): HTMLElement | null
Returns a reference to the first object with the specified value of the ID attribute.
props?: Record<string, any> | undefined
Component properties.
const props: {
foo: string;
}props.foo: string
foo = 'baz';对于 $destroy
,请使用 unmount
代替。
import { function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: MountOptions<Props>): Exports
Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
mount, function unmount(component: Record<string, any>, options?: {
outro?: boolean;
} | undefined): Promise<void>
Unmounts a component that was previously mounted using mount
or hydrate
.
Since 5.13.0, if options.outro
is true
, transitions will play before the component is removed from the DOM.
Returns a Promise
that resolves after transitions have completed if options.outro
is true, or immediately otherwise (prior to 5.13.0, returns void
).
import { mount, unmount } from 'svelte';
import App from './App.svelte';
const app = mount(App, { target: document.body });
// later...
unmount(app, { outro: true });
type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte'
const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
app.$destroy();
const const app: {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>app = mount<Record<string, any>, {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<...>>(component: ComponentType<...> | Component<...>, options: MountOptions<...>): {
...;
} & Record<...>Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
const App: LegacyComponentType
App, { target: Document | Element | ShadowRoot
Target element where the component will be mounted.
var document: Document
document.Document.getElementById(elementId: string): HTMLElement | null
Returns a reference to the first object with the specified value of the ID attribute.
function unmount(component: Record<string, any>, options?: {
outro?: boolean;
} | undefined): Promise<void>Unmounts a component that was previously mounted using mount
or hydrate
.
Since 5.13.0, if options.outro
is true
, transitions will play before the component is removed from the DOM.
Returns a Promise
that resolves after transitions have completed if options.outro
is true, or immediately otherwise (prior to 5.13.0, returns void
).
import { mount, unmount } from 'svelte';
import App from './App.svelte';
const app = mount(App, { target: document.body });
// later...
unmount(app, { outro: true });
const app: {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>app);作为权宜之计,您还可以使用 createClassComponent
或 asClassComponent
(从 svelte/legacy
导入)来保持 保持在实例化后与 Svelte 4 相同的 API。
import { function createClassComponent<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>, Slots extends Record<string, any>>(options: ComponentConstructorOptions<Props> & {
component: ComponentType<SvelteComponent<Props, Events, Slots>> | Component<Props>;
}): SvelteComponent<Props, Events, Slots> & Exports
Takes the same options as a Svelte 4 component and the component function and returns a Svelte 4 compatible component.
type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte'
const app = new App({ target: document.getElementById("app") });
const const app: SvelteComponent<Record<string, any>, any, any> & Record<string, any>
app = createClassComponent<Record<string, any>, Record<string, any>, any, any>(options: ComponentConstructorOptions<Record<string, any>> & {
component: Component<...> | ComponentType<...>;
}): SvelteComponent<...> & Record<...>Takes the same options as a Svelte 4 component and the component function and returns a Svelte 4 compatible component.
component: Component<Record<string, any>, {}, string> | ComponentType<SvelteComponent<Record<string, any>, any, any>>
component: const App: LegacyComponentType
App, ComponentConstructorOptions<Props extends Record<string, any> = Record<string, any>>.target: Document | Element | ShadowRoot
target: var document: Document
document.Document.getElementById(elementId: string): HTMLElement | null
Returns a reference to the first object with the specified value of the ID attribute.
const app: SvelteComponent<Record<string, any>, any, any> & Record<string, any>
app;如果这个组件不在您的控制之下,您可以使用 compatibility.componentApi
编译器选项来实现向后兼容性,这意味着使用 new Component(...)
的代码可以在不做调整的情况下继续工作(请注意,这会给每个组件增加一些开销)。这还将为您通过 bind:this
获取的所有组件实例添加 $set
和 $on
方法。
/// svelte.config.js
export default {
compilerOptions: {
compatibility: {
componentApi: number;
};
}
compilerOptions: {
compatibility: {
componentApi: number;
}
compatibility: {
componentApi: number
componentApi: 4
}
}
};
注意 mount
和 hydrate
不是同步的,因此类似 onMount
这样的内容在函数返回时不会被调用,待处理的 Promise 块尚未呈现(因为 #await
等待一个微任务以等待一个可能立即 resolve 的 Promise)。如果您需要这个保证,在调用 mount/hydrate
之后调用 flushSync
(从 'svelte'
导入)。
服务端 API 变化
同样,组件在服务端渲染编译时,不再具有 render
方法。相反,将函数传递给 svelte/server
的 render
:
import { function render<Comp extends SvelteComponent<any> | Component<any>, Props extends ComponentProps<Comp> = ComponentProps<Comp>>(...args: {} extends Props ? [component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp, options?: {
props?: Omit<Props, "$$slots" | "$$events">;
context?: Map<any, any>;
}] : [component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp, options: {
props: Omit<Props, "$$slots" | "$$events">;
context?: Map<any, any>;
}]): RenderOutput
Only available on the server and when compiling with the server
option.
Takes a component and returns an object with body
and head
properties on it, which you can use to populate the HTML when server-rendering your app.
type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte';
const { html, head } = App.render({ props: { message: 'hello' }});
const { const html: string
html, const head: string
HTML that goes into the <head>
render<SvelteComponent<Record<string, any>, any, any>, Record<string, any>>(component: ComponentType<SvelteComponent<Record<string, any>, any, any>>, options?: {
...;
} | undefined): RenderOutputOnly available on the server and when compiling with the server
option.
Takes a component and returns an object with body
and head
properties on it, which you can use to populate the HTML when server-rendering your app.
const App: LegacyComponentType
App, { props?: Omit<Record<string, any>, "$$slots" | "$$events"> | undefined
props: { message: string
message: 'hello' }});在 Svelte 4 中,将组件渲染为字符串也会返回所有组件的 CSS。在 Svelte 5 中,默认情况下不再这样,因为大多数情况下您使用工具链以其他方式处理它(例如 SvelteKit)。如果您需要从 render
返回 CSS,您可以将 css
编译器选项设置为 'injected'
,它将在 head
中添加 <style>
元素。
组件类型变化
从类到函数的变化也反映在类型定义中:SvelteComponent
,Svelte 4 的基类已被弃用,取而代之的是新的 Component
类型,它定义了 Svelte 组件的函数形状。要在 d.ts
文件中手动定义组件形状:
import type { interface Component<Props extends Record<string, any> = {}, Exports extends Record<string, any> = {}, Bindings extends keyof Props | "" = string>
Can be used to create strongly typed Svelte components.
Example:
You have component library on npm called component-library
, from which
you export a component called MyComponent
. For Svelte+TypeScript users,
you want to provide typings. Therefore you create a index.d.ts
:
import type { Component } from 'svelte';
export declare const MyComponent: Component<{ foo: string }> {}
Typing this makes it possible for IDEs like VS Code with the Svelte extension
to provide intellisense and to use the component like this in a Svelte file
with TypeScript:
<script lang="ts">
import { MyComponent } from "component-library";
</script>
<MyComponent foo={'bar'} />
Component } from 'svelte';
export declare const const MyComponent: Component<{
foo: string;
}, {}, string>
MyComponent: interface Component<Props extends Record<string, any> = {}, Exports extends Record<string, any> = {}, Bindings extends keyof Props | "" = string>
Can be used to create strongly typed Svelte components.
Example:
You have component library on npm called component-library
, from which
you export a component called MyComponent
. For Svelte+TypeScript users,
you want to provide typings. Therefore you create a index.d.ts
:
import type { Component } from 'svelte';
export declare const MyComponent: Component<{ foo: string }> {}
Typing this makes it possible for IDEs like VS Code with the Svelte extension
to provide intellisense and to use the component like this in a Svelte file
with TypeScript:
<script lang="ts">
import { MyComponent } from "component-library";
</script>
<MyComponent foo={'bar'} />
Component<{
foo: string
foo: string;
}>;
声明某种类型的组件是必需的:
<script lang="ts">
import type { SvelteComponent Component } from 'svelte';
import {
ComponentA,
ComponentB
} from 'component-library';
let component: typeof SvelteComponent<{ foo: string }>
let component: Component<{ foo: string }> = $state(
Math.random() ? ComponentA : ComponentB
);
</script>
<svelte:component this={component} foo="bar" />
两个工具类型 ComponentEvents
和 ComponentType
已被弃用。因为事件现在被定义为回调属性,而 ComponentEvents
已过时,因为新的 Component
类型已经是组件类型(例如 ComponentType<SvelteComponent<{ prop: string }>>
== Component<{ prop: string }>
)。
bind:this 变化
由于组件不再是类,使用 bind:this
不再返回带有 $set
、$on
和 $destroy
方法的类实例。它仅返回实例导出(export function/const
),并且如果您使用 accessors
选项,则返回每个属性的 getter/setter 对。
空格处理变化
此前,Svelte 使用了一个非常复杂的算法来确定是否应该保留空格。Svelte 5 简化了这一点,使开发人员更容易理解。规则如下:
- 节点之间的空格被折叠为一个空格
- 标签开头和结尾的空格被完全移除
- 某些例外情况,例如在
pre
标签内保留空格
和之前一样,您可以通过在编译器设置中将 preserveWhitespace
选项设置为 true
或在 <svelte:options>
中针对每个组件设置来禁用空格修剪。
需要现代浏览器
Svelte 5 需要现代浏览器(换句话说,不支持 Internet Explorer),原因如下:
- 它使用
Proxies
- 具有
clientWidth
/clientHeight
/offsetWidth
/offsetHeight
绑定的元素使用ResizeObserver
,而不是复杂的<iframe>
技巧 <input type="range" bind:value={...} />
仅使用input
事件监听器,而不是同时监听change
事件作为后备方案
legacy 编译器选项(该选项生成体积较大但兼容 IE 的代码)不再存在。
编译器选项的变化
- 从 css 选项中移除了
false
/true
(之前已经弃用)和"none"
这些有效值 legacy
选项被重新调整用途hydratable
选项已被移除。Svelte 组件现在始终是可水合的enableSourcemap
选项已被移除。现在始终生成 source map,工具可以选择忽略它tag
选项已被移除。请改用组件内的<svelte:options customElement="tag-name" />
loopGuardTimeout
、format
、sveltePath
、errorMode
和varsReport
选项已被移除
children 属性被保留
组件标签里的内容变为名为 children
的代码片段属性。你不能使用相同的名称定义其他属性。
点符号表示组件
在 Svelte 4 中,<foo.bar>
将创建一个标签名为 "foo.bar"
的元素。在 Svelte 5 中,foo.bar
被视为组件。这在 each
块中特别有用:
{#each items as item}
<item.component {...item.props} />
{/each}
符文模式中的重大变化
某些重大变更仅在组件处于符文模式时才适用。
不允许绑定到组件导出
符文模式下,组件的导出不能直接绑定。例如,组件 A
中有 export const foo = ...
,然后执行 <A bind:foo />
,将导致错误。使用 bind:this
代替——<A bind:this={a} />
——并通过 a.foo
访问导出。此更改使事情更容易理解,因为它强制了属性和导出之间的清晰分离。
绑定需要使用 $bindable() 显式定义
在 Svelte 4 语法中,每个属性(通过 export let
声明)都是可绑定的,这意味着您可以对其使用 bind:
。在符文模式中,属性默认不具有可绑定性:您需要使用 $bindable
符文来标记可绑定的 props。
如果一个可绑定属性有默认值(例如 let { foo = $bindable('bar') } = $props();
),当你要绑定该属性时,需要传递一个非 undefined
的值。这可以防止出现模棱两可的行为 —— 父组件和子组件必须具有相同的值,并能获得更好的性能(在 Svelte 4 中,默认值被反映回父组件,导致额外的无用渲染周期)。
accessors 选项被忽略
将 accessors
选项设置为 true
可使组件的属性在组件实例上直接访问。在符文模式下,属性永远不会在组件实例上访问。如果您需要暴露它们,可以使用组件导出。
immutable 选项被忽略
在符文模式下,设置 immutable
选项没有效果。这个概念被 $state
及其变体的工作方式所替代。
类不再是“自动响应式”
在 Svelte 4 中,执行以下操作会触发响应性:
<script>
let foo = new Foo();
</script>
<button on:click={() => (foo.value = 1)}>{foo.value}</button>
这是因为 Svelte 编译器将对 foo.value
的赋值视为更新所有引用 foo
的内容的指令。在 Svelte 5 中,响应性在运行时而不是编译时确定,因此您应该将 value
定义为 Foo
类上的响应式 $state
字段。将 new Foo()
包装在 $state(...)
中将不会产生任何效果——只有简单的对象和数组会被深度响应式化。
<svelte:component> 不再必要
在 Svelte 4 中,组件是 静态的 —— 如果您渲染 <Thing>
,并且 Thing
的值发生变化,不会发生任何事情。要使其动态,必须使用 <svelte:component>
。
在 Svelte 5 中,这不再成立:
<script>
import A from './A.svelte';
import B from './B.svelte';
let Thing = $state();
</script>
<select bind:value={Thing}>
<option value={A}>A</option>
<option value={B}>B</option>
</select>
<!-- 这些是等效的 -->
<Thing />
<svelte:component this={Thing} />
触控和滚轮事件是 passive
当使用 onwheel
、onmousewheel
、ontouchstart
和 ontouchmove
事件属性时,处理程序是 passive,以符合浏览器默认行为。这极大地提高了响应能力,因为浏览器可以立即滚动文档,而不是等待查看事件处理程序是否调用 event.preventDefault()
。
在极少数需要阻止这些事件默认行为的情况下,你应该使用 on
来代替(例如在 action 内部)。
Attribute / prop 语法更严格
在 Svelte 4 中,复杂的属性值不需要加引号:
<Component prop=this{is}valid />
这是一个潜在问题。在符文模式下,如果您希望连接内容,必须将值放在引号中:
<Component prop="this{is}valid" />
注意,如果你在 Svelte 5 中使用引号包裹单个表达式(例如 answer="{42}"
),也会收到警告 —— 在 Svelte 6 中,这将导致值被转换为字符串,而不是作为数字传递。
HTML 结构更严格
在Svelte 4中,你可以编写一些在服务器端渲染时会被浏览器修复的HTML代码。例如你可以这样写...
<table>
<tr>
<td>你好</td>
</tr>
</table>
...浏览器将自动插入 <tbody>
元素:
<table>
<tbody>
<tr>
<td>你好</td>
</tr>
</tbody>
</table>
Svelte 5 对 HTML 结构的要求更加严格,在浏览器会修复 DOM 的情况下会抛出编译错误。
其他重大变化
更严格的 @const 赋值验证
不再允许对const声明的解构部分进行赋值。这种操作本就不应该被允许。
:is(...) 和 :where(...) 是作用域的
以前,Svelte 不分析 :is(...)
和 :where(...)
内部的选择器,实际上会将它们视为全局选择器。Svelte 5 会在当前组件的上下文中分析它们。因此,如果某些选择器依赖于这种处理方式,现在可能会被视为未使用。要修复这个问题,请在 :is(...)/:where(...)
选择器内使用 :global(...)
。
在使用 Tailwind 的 @apply
指令时,添加 :global
选择器以保留使用 Tailwind 生成的 :is(...)
选择器的规则:
main :global {
@apply bg-blue-100 dark:bg-blue-900;
}
CSS 哈希位置不再具有确定性
以前,Svelte 总是会在最后插入 CSS 哈希值。在 Svelte 5 中这一点不再有保证。这只有在 有非常奇怪的 css 选择器 时才会导致问题。
作用域 CSS 使用 :where(...)
为了避免由不可预测的特异性变化引起的问题,作用域 CSS 选择器现在使用 .svelte-xyz123
(其中 xyz123
如前所述,是 <style>
内容的哈希)旁边使用 :where(.svelte-xyz123)
选择器修饰符。您可以在 这里 阅读更多细节。
如果您需要支持不实现 :where
的古老浏览器,您可以手动修改生成的 CSS,但代价是会产生不可预测的特异性变化:
css = css.replace(/:where\((.+?)\)/, '$1');
错误/警告代码已重命名
错误和警告代码已重命名。以前它们使用破折号分隔单词,现在使用下划线(例如,foo-bar 变为 foo_bar)。此外,一些代码的措辞也略有改动。
命名空间数量减少
您可以传递给编译器选项 namespace
的有效命名空间数量减少到 html
(默认)、mathml
和 svg
。
foreign
命名空间仅对 Svelte Native 有用,我们计划在 5.x 次要版本中以不同方式支持它。
beforeUpdate/afterUpdate 变更
如果 beforeUpdate
修改了模板中引用的变量,则在初始渲染时不再运行两次。
父组件中的 afterUpdate
回调现在将在任何子组件的 afterUpdate
回调之后运行。
当组件包含 <slot>
且其内容更新时,beforeUpdate/afterUpdate
不再运行。
这两个函数在符文模式下被禁止 —— 请改为使用 $effect.pre(...)
和 $effect(...)
。
contenteditable 行为变化
如果您有一个 contenteditable
节点,并且有一个对应的绑定 和 一个响应式值(例如:<div contenteditable=true bind:textContent>count is {count}</div>
),那么contenteditable 内的值不会因 count
的更新而更新,因为绑定会立即完全控制内容,且内容应该只通过绑定来更新。
oneventname 属性不再接受字符串值
在Svelte 4中,可以将 HTML 元素的事件属性指定为字符串:
<button onclick="alert('你好')">...</button>
不推荐这种做法,在 Svelte 5 中已不再可用,其中 onclick
属性替代 on:click
成为添加事件处理程序的机制。
null 和 undefined 变为空字符串
在 Svelte 4 中,null
和 undefined
会被打印为对应的字符串。在 100 个案例中,99 次您希望将其变为空字符串,而这也是其他大多数框架所做的。因此,在 Svelte 5 中,null
和 undefined
变为空字符串。
bind:files 值只能是 null、undefined 或 FileList
bind:files
现在是一个双向绑定。因此,在设置值时,它需要是 假值( null
或 undefined
)或 FileList
类型。
绑定现在会响应表单重置
之前,绑定不会考虑表单的 reset
事件,因此值可能会与 DOM 不同步。Svelte 5 通过在文档上放置 reset
监听器并在必要时调用绑定来修复这个问题。
walk 不再导出
svelte/compiler
为了方便从 estree-walker
重新导出了 walk
。在 Svelte 5 中,这种情况不再存在,如果需要请直接从该包中导入。
在 svelte:options 里的内容被禁止
在 Svelte 4 中,您可以在 <svelte:options />
标签内写入内容。它会被忽略,但您可以在里面写一些东西。在 Svelte 5 中,该标签里的内容会导致编译错误。
声明式 shadow roots 中的 <slot> 元素会被保留
Svelte 4 在所有地方都用自己版本的插槽替换了 <slot />
标签。Svelte 5 在这些标签作为 <template shadowrootmode="...">
元素的子元素时会保留它们。
<svelte:element> 标签必须是表达式
在 Svelte 4 中,<svelte:element this="div">
是有效的代码。这没有什么意义——您应该直接使用 <div>
。在极少数确实需要使用字面值的情况下,你可以这样做:
<svelte:element this={"div"}>
请注意,虽然 Svelte 4 会将 <svelte:element this="input">
(举例)与 <input>
视为相同,以确定可以应用哪些 bind:
指令。但 Svelte 5 不会这样做。
mount 默认播放过渡效果
用于渲染组件树的 mount
函数默认播放过渡效果,除非将 intro
选项设置为 false
。这与传统的类组件不同,后者在手动实例化时默认不播放过渡效果。
<img src={...}> 和 {@html ...} 水合不匹配不会被修复
在 Svelte 4 中,如果 src
属性或 {@html ...}
标签的值在服务端和客户端不同(即水合不匹配),这种不匹配会被修复。这个过程代价很高:设置 src
属性(即使它计算出相同的结果)会导致图像和 iframe 被重新加载,并且重新插入大量 HTML 是缓慢的。
由于这些不匹配极为罕见,Svelte 5 假定这些值保持不变,但在开发环境中如果它们不匹配会向你发出警告。要强制更新,你可以这样做:
<script>
let { markup, src } = $props();
if (typeof window !== 'undefined') {
// 储存值...
const initial = { markup, src };
// 取消设置它们...
markup = src = undefined;
$effect(() => {
// ...在我们挂载后重置
markup = initial.markup;
src = initial.src;
});
}
</script>
{@html markup}
<img {src} />
水合行为不同
Svelte 5 在服务端渲染期间使用注释,这些注释用于在客户端进行更健壮和高效的水合。因此,如果您打算对其进行水合,您不应该删除HTML输出中的注释,如果您手动编写了要由 Svelte 组件水合的 HTML,则需要在正确的位置添加这些注释。
onevent 属性被委托
事件属性替代事件指令:使用 onclick={handler}
而不是 on:click={handler}
。为了向后兼容,on:event
语法仍然受到支持,并且行为与 Svelte 4 中相同。然而,某些 onevent
属性是被委托的,这意味着您需要注意不要手动停止这些事件的传播,因为它们可能永远不会传递到根节点的该事件类型的监听器。
--style-props 使用了不同的元素
在使用 CSS 自定义属性时,Svelte 5
使用额外的<svelte-css-wrapper>
元素而不是 <div>
来包装组件。