Skip to main content

Svelte 5 迁移指南

版本 5 带有彻底改进的语法和反应系统。虽然乍一看可能有所不同,但你很快就会注意到许多相似之处。本指南详细介绍了这些变化并向你展示了如何升级。除此之外,我们还提供有关为什么进行这些更改的信息。

¥Version 5 comes with an overhauled syntax and reactivity system. While it may look different at first, you’ll soon notice many similarities. This guide goes over the changes in detail and shows you how to upgrade. Along with it, we also provide information on why we did these changes.

你不必立即迁移到新语法 - Svelte 5 仍然支持旧的 Svelte 4 语法,你可以将使用新语法的组件与使用旧语法的组件混合搭配,反之亦然。我们预计许多人能够仅通过最初更改几行代码即可升级。还有一个 迁移脚本 可以自动帮助你完成许多这些步骤。

¥You don’t have to migrate to the new syntax right away - Svelte 5 still supports the old Svelte 4 syntax, and you can mix and match components using the new syntax with components using the old and vice versa. We expect many people to be able to upgrade with only a few lines of code changed initially. There’s also a migration script that helps you with many of these steps automatically.

反应式语法更改(Reactivity syntax changes)

¥Reactivity syntax changes

Svelte 5 的核心是新的符文 API。符文基本上是编译器指令,用于通知 Svelte 有关反应性的信息。从语法上讲,符文是以美元符号开头的函数。

¥At the heart of Svelte 5 is the new runes API. Runes are basically compiler instructions that inform Svelte about reactivity. Syntactically, runes are functions starting with a dollar-sign.

let -> $state

在 Svelte 4 中,组件顶层的 let 声明是隐式响应的。在 Svelte 5 中,情况更加明确:使用 $state 符文创建时,变量是反应性的。让我们通过将计数器封装在 $state 中将计数器迁移到符文模式:

¥In Svelte 4, a let declaration at the top level of a component was implicitly reactive. In Svelte 5, things are more explicit: a variable is reactive when created using the $state rune. Let’s migrate the counter to runes mode by wrapping the counter in $state:

<script>
	let count = $state(0);
</script>

其他内容均无变化。count 仍然是数字本身,你可以直接读取和写入它,而不需要像 .valuegetCount() 这样的封装器。

¥Nothing else changes. count is still the number itself, and you read and write directly to it, without a wrapper like .value or getCount().

我们为什么这样做 let 在顶层隐式响应效果很好,但这意味着响应性受到限制 - 其他任何地方的 let 声明都不是反应性的。这迫使你在重构顶层组件中的代码以供重用时不得不使用存储。这意味着你必须学习一个完全独立的反应模型,而且结果通常不太好用。由于 Svelte 5 中的反应性更加明确,因此你可以在顶层组件之外继续使用相同的 API。前往 教程 了解更多信息。

¥[!DETAILS] Why we did this let being implicitly reactive at the top level worked great, but it meant that reactivity was constrained - a let declaration anywhere else was not reactive. This forced you to resort to using stores when refactoring code out of the top level of components for reuse. This meant you had to learn an entirely separate reactivity model, and the result often wasn’t as nice to work with. Because reactivity is more explicit in Svelte 5, you can keep using the same API outside the top level of components. Head to the tutorial to learn more.

$: -> $derived/$effect

在 Svelte 4 中,组件顶层的 $: 语句可用于声明派生,即完全通过计算其他状态来定义的状态。在 Svelte 5 中,这是使用 $derived 符文实现的:

¥In Svelte 4, a $: statement at the top level of a component could be used to declare a derivation, i.e. state that is entirely defined through a computation of other state. In Svelte 5, this is achieved using the $derived rune:

<script>
	let count = $state(0);
	$: const double = $derived(count * 2);
</script>

$state 一样,其他一切都没有变化。double 仍然是数字本身,你可以直接读取它,而不需要像 .valuegetDouble() 这样的封装器。

¥As with $state, nothing else changes. double is still the number itself, and you read it directly, without a wrapper like .value or getDouble().

$: 语句也可用于创建副作用。在 Svelte 5 中,这是使用 $effect 符文实现的:

¥A $: statement could also be used to create side effects. In Svelte 5, this is achieved using the $effect rune:

<script>
	let count = $state(0);
	$:$effect(() => {
		if (count > 5) {
			alert('Count is too high!');
		}
	});
</script>
我们为什么这样做 $: 是一种很好的简写,很容易上手:你可以在大多数代码前面加上 $:,它会以某种方式工作。这种直观性也是它的缺点,因为你的代码变得越复杂,它就越不容易推断。代码的意图是创建派生还是副作用?使用 $derived$effect,你需要进行更多的前期决策(剧透警告:90% 的时间你想要 $derived),但未来的你和你团队中的其他开发者将更轻松。

¥[!DETAILS] Why we did this $: was a great shorthand and easy to get started with: you could slap a $: in front of most code and it would somehow work. This intuitiveness was also its drawback the more complicated your code became, because it wasn’t as easy to reason about. Was the intent of the code to create a derivation, or a side effect? With $derived and $effect, you have a bit more up-front decision making to do (spoiler alert: 90% of the time you want $derived), but future-you and other developers on your team will have an easier time.

还有一些难以发现的陷阱:

¥There were also gotchas that were hard to spot:

  • $: 仅在渲染之前直接更新,这意味着你可以在重新渲染之间读取陈旧的值

    ¥$: only updated directly before rendering, which meant you could read stale values in-between rerenders

  • $: 每次运行一次,这意味着语句运行的频率可能比你想象的要低

    ¥$: only ran once per tick, which meant that statements may run less often than you think

  • $: 依赖是通过对依赖的静态分析确定的。这在大多数情况下都有效,但在重构期间可能会以微妙的方式中断,例如,依赖将被移入函数中,因此不再可见

    ¥$: dependencies were determined through static analysis of the dependencies. This worked in most cases, but could break in subtle ways during a refactoring where dependencies would be for example moved into a function and no longer be visible as a result

  • $: 语句也通过使用依赖的静态分析进行排序。在某些情况下,可能会出现平局,因此排序会出错,需要手动干预。重构代码时排序也可能会中断,因此某些依赖不再可见。

    ¥$: statements were also ordered by using static analysis of the dependencies. In some cases there could be ties and the ordering would be wrong as a result, needing manual interventions. Ordering could also break while refactoring code and some dependencies no longer being visible as a result.

最后,它不是 TypeScript 友好的(我们的编辑器工具必须经过一些考验才能使其对 TypeScript 有效),这阻碍了 Svelte 的反应模型真正通用。

¥Lastly, it wasn’t TypeScript-friendly (our editor tooling had to jump through some hoops to make it valid for TypeScript), which was a blocker for making Svelte’s reactivity model truly universal.

$derived$effect 通过以下方式修复所有这些问题

¥$derived and $effect fix all of these by

  • 始终返回最新值

    ¥always returning the latest value

  • 按需要频繁运行以保持稳定

    ¥running as often as needed to be stable

  • 在运行时确定依赖,因此不受重构的影响

    ¥determining the dependencies at runtime, and therefore being immune to refactorings

  • 根据需要执行依赖,因此不受排序问题的影响

    ¥executing dependencies as needed and therefore being immune to ordering problems

  • TypeScript 友好

    ¥being TypeScript-friendly

export let -> $props

在 Svelte 4 中,组件的属性使用 export let 声明。每个属性都是一个声明。在 Svelte 5 中,所有属性都通过 $props 符文通过解构声明:

¥In Svelte 4, properties of a component were declared using export let. Each property was one declaration. In Svelte 5, all properties are declared through the $props rune, through destructuring:

<script>
	export let optional = 'unset';
	export let required;
	let { optional = 'unset', required } = $props();
</script>

在多种情况下,声明属性变得不如拥有几个 export let 声明那么简单:

¥There are multiple cases where declaring properties becomes less straightforward than having a few export let declarations:

  • 你想重命名该属性,例如因为名称是保留标识符(例如 class

    ¥you want to rename the property, for example because the name is a reserved identifier (e.g. class)

  • 你不知道提前会期待哪些其他属性

    ¥you don’t know which other properties to expect in advance

  • 你想将每个属性转发到另一个组件

    ¥you want to forward every property to another component

所有这些情况都需要 Svelte 4 中的特殊语法:

¥All these cases need special syntax in Svelte 4:

  • 重置 元素export { klass as class}

    ¥renaming: export { klass as class}

  • paraglide (i18n)$$restProps

  • 所有属性 $$props

    ¥all properties $$props

在 Svelte 5 中,$props 符文使这变得简单,无需任何额外的 Svelte 特定语法:

¥In Svelte 5, the $props rune makes this straightforward without any additional Svelte-specific syntax:

  • 重置 元素使用属性重命名 let { class: klass } = $props();

    ¥renaming: use property renaming let { class: klass } = $props();

  • paraglide (i18n)使用扩展 let { foo, bar, ...rest } = $props();

    ¥other properties: use spreading let { foo, bar, ...rest } = $props();

  • 所有属性:不要改变 props。

    ¥all properties: don’t destructure let props = $props();

<script>
	let klass = '';
	export { klass as class};
	let { class: klass, ...rest } = $props();
</script>
<button class={klass} {...$$restPropsrest}>click me</button>
我们为什么这样做 export let 是比较有争议的 API 决定之一,关于是否应该考虑属性是 export 还是 import 存在很多争论。$props 没有这个特性。它也与其他符文一致,一般思路归结为 “进一步阅读”。

¥[!DETAILS] Why we did this export let was one of the more controversial API decisions, and there was a lot of debate about whether you should think about a property being exported or imported. $props doesn’t have this trait. It’s also in line with the other runes, and the general thinking reduces to “everything special to reactivity in Svelte is a rune”.

export let 也有很多限制,需要额外的 API,如上所示。$props 尝试克隆给定的值以返回不再改变的引用。

¥There were also a lot of limitations around export let, which required additional API, as shown above. $props unite this in one syntactical concept that leans heavily on regular JavaScript destructuring syntax.

事件变化(Event changes)

¥Event changes

事件处理程序在 Svelte 5 中得到了改头换面。而在 Svelte 4 中,我们使用 on: 指令将事件监听器附加到元素,而在 Svelte 5 中,它们与其他任何属性一样(换句话说 - 删除冒号):

¥Event handlers have been given a facelift in Svelte 5. Whereas in Svelte 4 we use the on: directive to attach an event listener to an element, in Svelte 5 they are properties like any other (in other words - remove the colon):

<script>
	let count = $state(0);
</script>

<button on:click={() => count++}>
	clicks: {count}
</button>

由于它们只是属性,因此你可以使用正常的简写语法……

¥Since they’re just properties, you can use the normal shorthand syntax...

<script>
	let count = $state(0);

	function onclick() {
		count++;
	}
</script>

<button {onclick}>
	clicks: {count}
</button>

...但是当使用命名事件处理函数时,通常最好使用更具描述性的名称。

¥...though when using a named event handler function it’s usually better to use a more descriptive name.

组件事件(Component events)

¥Component events

在 Svelte 4 中,组件可以通过使用 createEventDispatcher 创建调度程序来触发事件。

¥In Svelte 4, components could emit events by creating a dispatcher with createEventDispatcher.

此函数在 Svelte 5 中已弃用。相反,组件应该接受回调 props - 这意味着你将函数作为属性传递给这些组件:

¥This function is deprecated in Svelte 5. Instead, components should accept callback props - which means you then pass functions as properties to these components:

App
<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}>new balloon</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}>new balloon</button>
	<span class="boom">💥</span>
{:else}
	<span class="balloon" style="scale: {0.01 * size}">
		🎈
	</span>
{/if}
Pump
<script>
	import { createEventDispatcher } from 'svelte';
	const dispatch = createEventDispatcher();
	
	let { inflate, deflate } = $props();
	let power = $state(5);
</script>

<button onclick={() => dispatch('inflate', power)inflate(power)}>
	inflate
</button>
<button onclick={() => dispatch('deflate', power)deflate(power)}>
	deflate
</button>
<button onclick={() => power--}>-</button>
Pump power: {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)}>
	inflate
</button>
<button onclick={() => dispatch('deflate', power)deflate(power)}>
	deflate
</button>
<button onclick={() => power--}>-</button>
Pump power: {power}
<button onclick={() => power++}>+</button>

冒泡事件(Bubbling events)

¥Bubbling events

组件不应将事件从元素传递到组件,而应接受 onclick 回调 prop:

¥Instead of doing <button on:click> to ‘forward’ the event from the element to the component, the component should accept an onclick callback prop:

<script>
	let { onclick } = $props();
</script>

<button on:click {onclick}>
	click me
</button>

请注意,这也意味着你可以将 ‘spread’ 事件处理程序与其他属性一起放到元素上,而不是繁琐地单独转发每个事件:

¥Note that this also means you can ‘spread’ event handlers onto the element along with other props instead of tediously forwarding each event separately:

<script>
	let props = $props();
</script>

<button {...$$props} on:click on:keydown on:all_the_other_stuff {...props}>
	click me
</button>

事件修饰符(Event modifiers)

¥Event modifiers

在 Svelte 4 中,你可以向处理程序添加事件修饰符:

¥In Svelte 4, you can add event modifiers to handlers:

<button on:click|once|preventDefault={handler}>...</button>

修饰符特定于 on:,因此不适用于现代事件处理程序。在处理程序本身内部添加诸如 event.preventDefault() 之类的内容是可取的,因为所有逻辑都集中在一个地方,而不是分散在处理程序和修饰符之间。

¥Modifiers are specific to on: and as such do not work with modern event handlers. Adding things like event.preventDefault() inside the handler itself is preferable, since all the logic lives in one place rather than being split between handler and modifiers.

由于事件处理程序只是函数,你可以根据需要创建自己的封装器:

¥Since event handlers are just functions, you can create your own wrappers as necessary:

<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>

有三个修饰符 — capturepassivenonpassive — 不能表示为封装函数,因为它们需要在绑定事件处理程序时应用,而不是在运行时应用。

¥There are three modifiers — capture, passive and nonpassive — that can’t be expressed as wrapper functions, since they need to be applied when the event handler is bound rather than when it runs.

对于 capture,我们将修饰符添加到事件名称:

¥For capture, we add the modifier to the event name:

<button onclickcapture={...}>...</button>

同时,更改事件处理程序的 passive 选项不是一件可以轻易完成的事情。如果你有它的用例 — 而你可能没有!— 那么你将需要使用操作来自己应用事件处理程序。

¥Changing the passive option of an event handler, meanwhile, is not something to be done lightly. If you have a use case for it — and you probably don’t! — then you will need to use an action to apply the event handler yourself.

多个事件处理程序(Multiple event handlers)

¥Multiple event handlers

在 Svelte 4 中,这是可能的:

¥In Svelte 4, this is possible:

<button on:click={one} on:click={two}>...</button>

不允许在元素上重复属性/特性 - 现在包括事件处理程序。而是这样做:

¥Duplicate attributes/properties on elements — which now includes event handlers — are not allowed. Instead, do this:

<button
	onclick={(e) => {
		one(e);
		two(e);
	}}
>
	...
</button>

当传播 props 时,本地事件处理程序必须在传播之后,否则它们有被覆盖的风险:

¥When spreading props, local event handlers must go after the spread, or they risk being overwritten:

<button
	{...props}
	onclick={(e) => {
		doStuff(e);
		props.onclick?.(e);
	}}
>
	...
</button>
我们为什么这样做 createEventDispatcher 总是有点样板化:

¥[!DETAILS] Why we did this createEventDispatcher was always a bit boilerplate-y:

  • 继承来自

    ¥import the function

  • 调用该函数以获取调度函数

    ¥call the function to get a dispatch function

  • 使用字符串和可能的负载调用所述调度函数

    ¥call said dispatch function with a string and possibly a payload

  • 通过 .detail 属性在另一端检索所述有效负载,因为事件本身始终是 CustomEvent

    ¥retrieve said payload on the other end through a .detail property, because the event itself was always a CustomEvent

始终可以使用组件回调 props,但由于你必须使用 on: 监听 DOM 事件,因此出于语法一致性的考虑,对组件事件使用 createEventDispatcher 是有意义的。现在我们有了事件属性(onclick),情况正好相反:回调 props 现在是更明智的做法。

¥It was always possible to use component callback props, but because you had to listen to DOM events using on:, it made sense to use createEventDispatcher for component events due to syntactical consistency. Now that we have event attributes (onclick), it’s the other way around: Callback props are now the more sensible thing to do.

删除事件修饰符可以说是那些喜欢事件修饰符简写语法的人似乎倒退的变化之一。鉴于它们不经常使用,我们用较小的表面积换取更明确性。修饰符也不一致,因为它们中的大多数只能在 DOM 元素上使用。

¥The removal of event modifiers is arguably one of the changes that seems like a step back for those who’ve liked the shorthand syntax of event modifiers. Given that they are not used that frequently, we traded a smaller surface area for more explicitness. Modifiers also were inconsistent, because most of them were only useable on DOM elements.

同一事件的多个监听器也不再可能,但它无论如何都是一种反模式,因为它妨碍了可读性:如果有很多属性,除非它们彼此紧挨着,否则很难发现有两个处理程序。它还意味着两个处理程序是独立的,而实际上 one 内部的 event.stopImmediatePropagation() 之类的东西会阻止调用 two

¥Multiple listeners for the same event are also no longer possible, but it was something of an anti-pattern anyway, since it impedes readability: if there are many attributes, it becomes harder to spot that there are two handlers unless they are right next to each other. It also implies that the two handlers are independent, when in fact something like event.stopImmediatePropagation() inside one would prevent two from being called.

通过弃用 createEventDispatcheron: 指令,转而使用回调 props 和普通元素属性,我们:

¥By deprecating createEventDispatcher and the on: directive in favour of callback props and normal element properties, we:

  • 拒绝新请求()

    ¥reduce Svelte’s learning curve

  • 删除样板,特别是围绕 createEventDispatcher

    ¥remove boilerplate, particularly around createEventDispatcher

  • 删除为可能甚至没有监听器的事件创建 CustomEvent 对象的开销

    ¥remove the overhead of creating CustomEvent objects for events that may not even have listeners

  • 添加传播事件处理程序的能力

    ¥add the ability to spread event handlers

  • 添加知道为组件提供了哪些事件处理程序的能力

    ¥add the ability to know which event handlers were provided to a component

  • 添加表达给定事件处理程序是必需的还是可选的能力

    ¥add the ability to express whether a given event handler is required or optional

  • 增加类型安全性(以前,Svelte 几乎不可能保证组件不会发出特定事件)

    ¥increase type safety (previously, it was effectively impossible for Svelte to guarantee that a component didn’t emit a particular event)

代码片段代替插槽(Snippets instead of slots)

¥Snippets instead of slots

在 Svelte 4 中,可以使用插槽将内容传递给组件。Svelte 5 用功能更强大、更灵活的代码片段取代了它们,因此 Svelte 5 中已弃用插槽。

¥In Svelte 4, content can be passed to components using slots. Svelte 5 replaces them with snippets which are more powerful and flexible, and as such slots are deprecated in Svelte 5.

但是,它们继续工作,你可以将代码片段传递给使用插槽的组件:

¥They continue to work, however, and you can pass snippets to a component that uses slots:

Child
<slot />
<hr />
<slot name="foo" message="hello" />
Parent
<script>
	import Child from './Child.svelte';
</script>

<Child>
	default child content

	{#snippet foo({ message })}
		message from child: {message}
	{/snippet}
</Child>
<script lang="ts">
	import Child from './Child.svelte';
</script>

<Child>
	default child content

	{#snippet foo({ message })}
		message from child: {message}
	{/snippet}
</Child>

(反之则不然 - 你不能将插槽内容传递给使用 {@render ...} 标记的组件。)

¥(The reverse is not true — you cannot pass slotted content to a component that uses {@render ...} tags.)

使用自定义元素时,你仍应像以前一样使用 <slot />。在未来版本中,当 Svelte 删除其内部版本的插槽时,它将保留这些插槽原样,即输出常规 DOM 标签而不是对其进行转换。

¥When using custom elements, you should still use <slot /> like before. In a future version, when Svelte removes its internal version of slots, it will leave those slots as-is, i.e. output a regular DOM tag instead of transforming it.

默认内容(Default content)

¥Default content

在 Svelte 4 中,将 UI 传递给子组件的最简单方法是使用 <slot />。在 Svelte 5 中,这是使用 children prop 完成的,然后用 {@render children()} 显示:

¥In Svelte 4, the easiest way to pass a piece of UI to the child was using a <slot />. In Svelte 5, this is done using the children prop instead, which is then shown with {@render children()}:

<script>
	let { children } = $props();
</script>

<slot />
{@render children?.()}

多个内容占位符(Multiple content placeholders)

¥Multiple content placeholders

如果你想要多个 UI 占位符,则必须使用命名插槽。在 Svelte 5 中,改用 props,随意命名它们并 {@render ...} 它们:

¥If you wanted multiple UI placeholders, you had to use named slots. In Svelte 5, use props instead, name them however you like and {@render ...} them:

<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>

传递数据备份(Passing data back up)

¥Passing data back up

在 Svelte 4 中,你会将数据传递给 <slot />,然后使用父组件中的 let: 检索它。在 Svelte 5 中,代码片段承担了这一责任:

¥In Svelte 4, you would pass data to a <slot /> and then retrieve it with let: in the parent component. In Svelte 5, snippets take on that responsibility:

App
<script>
	import List from './List.svelte';
</script>

<List items={['one', 'two', 'three']} let:item>
	{#snippet item(text)}
		<span>{text}</span>
	{/snippet}
	<span slot="empty">No items yet</span>
	{#snippet empty()}
		<span>No items yet</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">No items yet</span>
	{#snippet empty()}
		<span>No items yet</span>
	{/snippet}
</List>
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}
我们为什么这样做 Slots 很容易上手,但用例越高级,语法就越复杂和混乱:

¥[!DETAILS] Why we did this Slots were easy to get started with, but the more advanced the use case became, the more involved and confusing the syntax became:

  • let: 语法让很多人感到困惑,因为它会创建一个变量,而所有其他 : 指令都会接收一个变量

    ¥the let: syntax was confusing to many people as it creates a variable whereas all other : directives receive a variable

  • let: 声明的变量的范围不明确。在上面的例子中,你可能看起来可以在 empty 插槽中使用 item 插槽 prop,但事实并非如此

    ¥the scope of a variable declared with let: wasn’t clear. In the example above, it may look like you can use the item slot prop in the empty slot, but that’s not true

  • 必须使用 slot 属性将命名插槽应用于元素。有时你不想创建元素,因此我们必须添加 <svelte:fragment> API

    ¥named slots had to be applied to an element using the slot attribute. Sometimes you didn’t want to create an element, so we had to add the <svelte:fragment> API

  • 命名插槽也可以应用于组件,这改变了 let: 指令可用位置的语义(即使在今天,我们维护人员也常常不知道如何解决它有效)

    ¥named slots could also be applied to a component, which changed the semantics of where let: directives are available (even today us maintainers often don’t know which way around it works)

Snippet 通过更易读和更清晰地解决了所有这些问题。同时,它们更强大,因为它们允许你定义可以在任何地方渲染的 UI 部分,而不仅仅是将它们作为 props 传递给组件。

¥Snippets solve all of these problems by being much more readable and clear. At the same time they’re more powerful as they allow you to define sections of UI that you can render anywhere, not just passing them as props to a component.

迁移脚本(Migration script)

¥Migration script

现在你应该对之前/之后以及旧语法与新语法的关系有相当好的理解。可能还很明显,这些迁移中的很多都是相当技术性和重复性的 - 你不想手动执行的操作。

¥By now you should have a pretty good understanding of the before/after and how the old syntax relates to the new syntax. It probably also became clear that a lot of these migrations are rather technical and repetitive - something you don’t want to do by hand.

我们也这么想,这就是我们提供迁移脚本来自动补齐大部分迁移的原因。你可以使用 npx sv migrate svelte-5 升级你的项目。这将执行以下操作:

¥We thought the same, which is why we provide a migration script to do most of the migration automatically. You can upgrade your project by using npx sv migrate svelte-5. This will do the following things:

  • 在你的 package.json 中增加核心依赖

    ¥bump core dependencies in your package.json

  • 迁移到符文(let -> $state 等)

    ¥migrate to runes (let -> $state etc)

  • 迁移到 DOM 元素的事件属性(on:click -> onclick

    ¥migrate to event attributes for DOM elements (on:click -> onclick)

  • 将插槽创建迁移到渲染标签(<slot /> -> {@render children()}

    ¥migrate slot creations to render tags (<slot /> -> {@render children()})

  • 将插槽使用迁移到片段(<div slot="x">...</div> -> {#snippet x()}<div>...</div>{/snippet}

    ¥migrate slot usages to snippets (<div slot="x">...</div> -> {#snippet x()}<div>...</div>{/snippet})

  • 迁移明显的组件创建(new Component(...) -> mount(Component, ...)

    ¥migrate obvious component creations (new Component(...) -> mount(Component, ...))

你还可以通过 Migrate Component to Svelte 5 Syntax 命令在 VS Code 中迁移单个组件,或者通过 Migrate 按钮在我们的 Playground 中迁移单个组件。

¥You can also migrate a single component in VS Code through the Migrate Component to Svelte 5 Syntax command, or in our Playground through the Migrate button.

并非所有内容都可以自动迁移,并且某些迁移之后需要手动清理。以下部分更详细地描述了这些。

¥Not everything can be migrated automatically, and some migrations need manual cleanup afterwards. The following sections describe these in more detail.

run

你可能会看到迁移脚本将某些 $: 语句转换为从 svelte/legacy 导入的 run 函数。如果迁移脚本无法可靠地将语句迁移到 $derived 并得出结论这是一个副作用,就会发生这种情况。在某些情况下,这可能是错误的,最好将其更改为使用 $derived。在其他情况下它可能是正确的,但由于 $: 语句也在服务器上运行,而 $effect 没有运行,因此将其转换为这样是不安全的。相反,run 被用作权宜之计。run 模仿了 $: 的大部分特性,它在服务器上运行一次,并在客户端上作为 $effect.pre 运行($effect.pre 在将更改应用于 DOM 之前运行;你很可能想使用 $effect)。

¥You may see that the migration script converts some of your $: statements to a run function which is imported from svelte/legacy. This happens if the migration script couldn’t reliably migrate the statement to a $derived and concluded this is a side effect instead. In some cases this may be wrong and it’s best to change this to use a $derived instead. In other cases it may be right, but since $: statements also ran on the server but $effect does not, it isn’t safe to transform it as such. Instead, run is used as a stopgap solution. run mimics most of the characteristics of $:, in that it runs on the server once, and runs as $effect.pre on the client ($effect.pre runs before changes are applied to the DOM; most likely you want to use $effect instead).

<script>
	import { run } from 'svelte/legacy';
	run(() => {
	$effect(() => {
		// some side effect code
	})
</script>

事件修饰符(Event modifiers)

¥Event modifiers

事件修饰符不适用于事件属性(例如,你不能执行 onclick|preventDefault={...})。因此,在将事件指令迁移到事件属性时,我们需要这些修饰符的函数替换。这些是从 svelte/legacy 导入的,应该迁移出去,例如只使用 event.preventDefault()

¥Event modifiers are not applicable to event attributes (e.g. you can’t do onclick|preventDefault={...}). Therefore, when migrating event directives to event attributes, we need a function-replacement for these modifiers. These are imported from svelte/legacy, and should be migrated away from in favor of e.g. just using event.preventDefault().

<script>
	import { preventDefault } from 'svelte/legacy';
</script>

<button
	onclick={preventDefault((event) => {
		event.preventDefault();
		// ...
	})}
>
	click me
</button>

未自动迁移的内容(Things that are not automigrated)

¥Things that are not automigrated

迁移脚本不会转换 createEventDispatcher。你需要手动调整这些部分。它不这样做是因为风险太大,因为它可能会导致组件用户的中断,而迁移脚本无法发现这一点。

¥The migration script does not convert createEventDispatcher. You need to adjust those parts manually. It doesn’t do it because it’s too risky because it could result in breakage for users of the component, which the migration script cannot find out.

迁移脚本不会转换 beforeUpdate/afterUpdate。它不这样做是因为无法确定代码的实际意图。根据经验,你通常可以使用 $effect.pre(与 beforeUpdate 同时运行)和 tick(从 svelte 导入,允许你等到更改应用于 DOM 然后再执行一些工作)的组合。

¥The migration script does not convert beforeUpdate/afterUpdate. It doesn’t do it because it’s impossible to determine the actual intent of the code. As a rule of thumb you can often go with a combination of $effect.pre (runs at the same time as beforeUpdate did) and tick (imported from svelte, allows you to wait until changes are applied to the DOM and then do some work).

组件不再是类(Components are no longer classes)

¥Components are no longer classes

在 Svelte 3 和 4 中,组件是类。在 Svelte 5 中,它们是函数,应该以不同的方式实例化。如果你需要手动实例化组件,则应改用 mounthydrate(从 svelte 导入)。如果你使用 SvelteKit 时看到此错误,请先尝试更新到最新版本的 SvelteKit,该版本增加了对 Svelte 5 的支持。如果你使用的是没有 SvelteKit 的 Svelte,则可能需要调整 main.js 文件(或类似文件):

¥In Svelte 3 and 4, components are classes. In Svelte 5 they are functions and should be instantiated differently. If you need to manually instantiate components, you should use mount or hydrate (imported from svelte) instead. If you see this error using SvelteKit, try updating to the latest version of SvelteKit first, which adds support for Svelte 5. If you’re using Svelte without SvelteKit, you’ll likely have a main.js file (or similar) which you need to adjust:

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.

mount
(const App: LegacyComponentTypeApp, { target: Document | Element | ShadowRoot

Target element where the component will be mounted.

target
: var document: Documentdocument.Document.getElementById(elementId: string): HTMLElement | null

Returns a reference to the first object with the specified value of the ID attribute.

@paramelementId String that specifies the ID value.
getElementById
("app") });
export default
const app: {
    $on?(type: string, callback: (e: any) => void): () => void;
    $set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>
app
;

mounthydrate 具有完全相同的 API。不同之处在于 hydrate 将在其目标内拾取 Svelte 的服务器渲染 HTML 并对其进行水化。两者都返回一个对象,其中包含组件的导出和潜在的属性访问器(如果使用 accessors: true 编译)。它们不附带你可能从类组件 API 中了解的 $on$set$destroy 方法。这些是它的替代品:

¥mount and hydrate have the exact same API. The difference is that hydrate will pick up the Svelte’s server-rendered HTML inside its target and hydrate it. Both return an object with the exports of the component and potentially property accessors (if compiled with accessors: true). They do not come with the $on, $set and $destroy methods you may know from the class component API. These are its replacements:

对于 $on,不是监听事件,而是通过选项参数上的 events 属性传递它们。

¥For $on, instead of listening to events, pass them via the events property on the options argument.

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.

mount
(const App: LegacyComponentTypeApp, { target: Document | Element | ShadowRoot

Target element where the component will be mounted.

target
: var document: Documentdocument.Document.getElementById(elementId: string): HTMLElement | null

Returns a reference to the first object with the specified value of the ID attribute.

@paramelementId String that specifies the ID value.
getElementById
("app"), events?: Record<string, (e: any) => any> | undefined

Allows the specification of events.

@deprecatedUse callback props instead.
events
: { event: anyevent: callback } });

请注意,不鼓励使用 events — 而是 使用回调

¥[!NOTE] Note that using events is discouraged — instead, use callbacks

对于 $set,请改用 $state 来创建反应性属性对象并对其进行操作。如果你在 .js.ts 文件中执行此操作,请调整结尾以包含 .svelte,即 .svelte.js.svelte.ts

¥For $set, use $state instead to create a reactive property object and manipulate it. If you’re doing this inside a .js or .ts file, adjust the ending to include .svelte, i.e. .svelte.js or .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

Declares reactive state.

Example:

let count = $state(0);

https://svelte.dev/docs/svelte/$state

@paraminitial The initial value
$state
({ foo: stringfoo: '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.

mount
(const App: LegacyComponentTypeApp, { target: Document | Element | ShadowRoot

Target element where the component will be mounted.

target
: var document: Documentdocument.Document.getElementById(elementId: string): HTMLElement | null

Returns a reference to the first object with the specified value of the ID attribute.

@paramelementId String that specifies the ID value.
getElementById
("app"), props?: Record<string, any> | undefined

Component properties.

props
});
const props: {
    foo: string;
}
props
.foo: stringfoo = 'baz';

对于 $destroy,请改用 unmount

¥For $destroy, use unmount instead.

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 });
unmount
} 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.$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.

mount
(const App: LegacyComponentTypeApp, { target: Document | Element | ShadowRoot

Target element where the component will be mounted.

target
: var document: Documentdocument.Document.getElementById(elementId: string): HTMLElement | null

Returns a reference to the first object with the specified value of the ID attribute.

@paramelementId String that specifies the ID value.
getElementById
("app") });
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 });
unmount
(
const app: {
    $on?(type: string, callback: (e: any) => void): () => void;
    $set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>
app
);

作为权宜之计,你还可以使用 createClassComponentasClassComponent(从 svelte/legacy 导入)来在实例化后保持与 Svelte 4 相同的 API。

¥As a stop-gap-solution, you can also use createClassComponent or asClassComponent (imported from svelte/legacy) instead to keep the same API known from Svelte 4 after instantiating.

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.

@deprecatedUse this only as a temporary solution to migrate your imperative component code to Svelte 5.
createClassComponent
} from 'svelte/legacy';
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: 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.

@deprecatedUse this only as a temporary solution to migrate your imperative component code to Svelte 5.
createClassComponent
({ component: Component<Record<string, any>, {}, string> | ComponentType<SvelteComponent<Record<string, any>, any, any>>component: const App: LegacyComponentTypeApp, ComponentConstructorOptions<Props extends Record<string, any> = Record<string, any>>.target: Document | Element | ShadowRoottarget: var document: Documentdocument.Document.getElementById(elementId: string): HTMLElement | null

Returns a reference to the first object with the specified value of the ID attribute.

@paramelementId String that specifies the ID value.
getElementById
("app") });
export default const app: SvelteComponent<Record<string, any>, any, any> & Record<string, any>app;

如果此组件不受你的控制,你可以使用 compatibility.componentApi 编译器选项自动应用向后兼容性,这意味着使用 new Component(...) 的代码无需调整即可继续工作(请注意,这会给每个组件增加一些开销)。这还将为你通过 bind:this 获得的所有组件实例添加 $set$on 方法。

¥If this component is not under your control, you can use the compatibility.componentApi compiler option for auto-applied backwards compatibility, which means code using new Component(...) keeps working without adjustments (note that this adds a bit of overhead to each component). This will also add $set and $on methods for all component instances you get through bind:this.

/// svelte.config.js
export default {
	
compilerOptions: {
    compatibility: {
        componentApi: number;
    };
}
compilerOptions
: {
compatibility: {
    componentApi: number;
}
compatibility
: {
componentApi: numbercomponentApi: 4 } } };

请注意,mounthydrate 不是同步的,因此在函数返回时不会调用 onMount 之类的东西,并且待处理的 promise 块尚未渲染(因为 #await 等待微任务以等待可能立即解决的 promise)。如果你需要该保证,请在调用 mount/hydrate 后调用 flushSync(从 'svelte' 导入)。

¥Note that mount and hydrate are not synchronous, so things like onMount won’t have been called by the time the function returns and the pending block of promises will not have been rendered yet (because #await waits a microtask to wait for a potentially immediately-resolved promise). If you need that guarantee, call flushSync (import from 'svelte') after calling mount/hydrate.

服务器 API 更改(Server API changes)

¥Server API changes

同样,在为服务器端渲染编译时,组件不再具有 render 方法。相反,将函数从 svelte/server 传递给 render

¥Similarly, components no longer have a render method when compiled for server side rendering. Instead, pass the function to render from svelte/server:

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.

render
} from 'svelte/server';
import
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: stringhtml, const head: string

HTML that goes into the &#x3C;head>

head
} =
render<SvelteComponent<Record<string, any>, any, any>, Record<string, any>>(component: ComponentType<SvelteComponent<Record<string, any>, any, any>>, options?: {
    ...;
} | undefined): 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.

render
(const App: LegacyComponentTypeApp, { props?: Omit<Record<string, any>, "$$slots" | "$$events"> | undefinedprops: { message: stringmessage: 'hello' }});

在 Svelte 4 中,将组件渲染为字符串还会返回所有组件的 CSS。在 Svelte 5 中,默认情况下不再是这种情况,因为大多数时候你都在使用以其他方式处理它的工具链(如 SvelteKit)。如果你需要从 render 返回 CSS,你可以将 css 编译器选项设置为 'injected',它会将 <style> 元素添加到 head

¥In Svelte 4, rendering a component to a string also returned the CSS of all components. In Svelte 5, this is no longer the case by default because most of the time you’re using a tooling chain that takes care of it in other ways (like SvelteKit). If you need CSS to be returned from render, you can set the css compiler option to 'injected' and it will add <style> elements to the head.

组件类型更改(Component typing changes)

¥Component typing changes

从类到函数的变化也反映在类型中:SvelteComponent 是 Svelte 4 中的基类,已弃用,取而代之的是新的 Component 类型,该类型定义了 Svelte 组件的函数形状。要在 d.ts 文件中手动定义组件形状:

¥The change from classes towards functions is also reflected in the typings: SvelteComponent, the base class from Svelte 4, is deprecated in favour of the new Component type which defines the function shape of a Svelte component. To manually define a component shape in a d.ts file:

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&#x3C;{ 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:

&#x3C;script lang="ts">
	import { MyComponent } from "component-library";
&#x3C;/script>
&#x3C;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&#x3C;{ 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:

&#x3C;script lang="ts">
	import { MyComponent } from "component-library";
&#x3C;/script>
&#x3C;MyComponent foo={'bar'} />
Component
<{
foo: stringfoo: string; }>;

要声明需要某种类型的组件:

¥To declare that a component of a certain type is required:

import { import ComponentAComponentA, import ComponentBComponentB } from 'component-library';
import type { SvelteComponent } from 'svelte';
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&#x3C;{ 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:

&#x3C;script lang="ts">
	import { MyComponent } from "component-library";
&#x3C;/script>
&#x3C;MyComponent foo={'bar'} />
Component
} from 'svelte';
let C: typeof SvelteComponent<{ foo: string }> = $state( let
let C: Component<{
    foo: string;
}, {}, string>
C
: 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&#x3C;{ 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:

&#x3C;script lang="ts">
	import { MyComponent } from "component-library";
&#x3C;/script>
&#x3C;MyComponent foo={'bar'} />
Component
<{ foo: stringfoo: string }> =
function $state<any>(initial: any): any (+1 overload)
namespace $state

Declares reactive state.

Example:

let count = $state(0);

https://svelte.dev/docs/svelte/$state

@paraminitial The initial value
$state
(
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.Math.random(): number

Returns a pseudorandom number between 0 and 1.

random
() ? import ComponentAComponentA : import ComponentBComponentB
);

两种工具类型 ComponentEventsComponentType 也已弃用。ComponentEvents 已过时,因为事件现在被定义为回调 props,而 ComponentType 已过时,因为新的 Component 类型已经是组件类型(即 ComponentType<SvelteComponent<{ prop: string }>> 相当于 Component<{ prop: string }>)。

¥The two utility types ComponentEvents and ComponentType are also deprecated. ComponentEvents is obsolete because events are defined as callback props now, and ComponentType is obsolete because the new Component type is the component type already (i.e. ComponentType<SvelteComponent<{ prop: string }>> is equivalent to Component<{ prop: string }>).

bind:this 更改(bind:this changes)

¥bind:this changes

由于组件不再是类,使用 bind:this 不再返回带有 $set$on$destroy 方法的类实例。它仅返回实例导出(export function/const),如果你使用 accessors 选项,则返回每个属性的 getter/setter 对。

¥Because components are no longer classes, using bind:this no longer returns a class instance with $set, $on and $destroy methods on it. It only returns the instance exports (export function/const) and, if you’re using the accessors option, a getter/setter-pair for each property.

<svelte:component> 不再需要(<svelte:component> is no longer necessary)

¥<svelte:component> is no longer necessary

在 Svelte 4 中,组件是静态的 - 如果你渲染 <Thing>,并且 Thing 的值发生变化,则 什么都没有发生。要使其动态化,你必须使用 <svelte:component>

¥In Svelte 4, components are static — if you render <Thing>, and the value of Thing changes, nothing happens. To make it dynamic you had to use <svelte:component>.

这在 Svelte 5 中不再适用:

¥This is no longer true in 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>

<!-- these are equivalent -->
<Thing />
<svelte:component this={Thing} />

在迁移时,请记住,除非使用点符号,否则组件的名称应大写(Thing)以将其与元素区分开来。

¥While migrating, keep in mind that your component’s name should be capitalized (Thing) to distinguish it from elements, unless using dot notation.

点符号表示组件(Dot notation indicates a component)

¥Dot notation indicates a component

在 Svelte 4 中,<foo.bar> 将创建一个带有标签名称 "foo.bar" 的元素。在 Svelte 5 中,foo.bar 被视为组件。这在 each 块内特别有用:

¥In Svelte 4, <foo.bar> would create an element with a tag name of "foo.bar". In Svelte 5, foo.bar is treated as a component instead. This is particularly useful inside each blocks:

{#each items as item}
	<item.component {...item.props} />
{/each}

空格处理已更改(Whitespace handling changed)

¥Whitespace handling changed

以前,Svelte 使用了一种非常复杂的算法来确定是否应保留空格。Svelte 5 简化了这一过程,使开发者更容易推断。规则如下:

¥Previously, Svelte employed a very complicated algorithm to determine if whitespace should be kept or not. Svelte 5 simplifies this which makes it easier to reason about as a developer. The rules are:

  • 节点之间的空格被折叠为一个空格

    ¥Whitespace between nodes is collapsed to one whitespace

  • 标签开头和结尾的空格被完全删除

    ¥Whitespace at the start and end of a tag is removed completely

  • 某些例外情况适用,例如保留 pre 标签内的空格

    ¥Certain exceptions apply such as keeping whitespace inside pre tags

与以前一样,你可以通过在编译器设置中或在 <svelte:options> 中按组件设置 preserveWhitespace 选项来禁用空格修剪。

¥As before, you can disable whitespace trimming by setting the preserveWhitespace option in your compiler settings or on a per-component basis in <svelte:options>.

现代浏览器必需(Modern browser required)

¥Modern browser required

Svelte 5 需要现代浏览器(换句话说,不是 Internet Explorer),原因如下:

¥Svelte 5 requires a modern browser (in other words, not Internet Explorer) for various reasons:

  • 仅 JavaScript

    ¥it uses Proxies

  • 具有 clientWidth / clientHeight/offsetWidth/offsetHeight 绑定的元素使用 ResizeObserver 而不是复杂的 <iframe> 黑客

    ¥elements with clientWidth / clientHeight/offsetWidth/offsetHeight bindings use a ResizeObserver rather than a convoluted <iframe> hack

  • <input type="range" bind:value={...} /> 仅使用 input 事件监听器,而不是同时监听 change 事件作为后备

    ¥<input type="range" bind:value={...} /> only uses an input event listener, rather than also listening for change events as a fallback

legacy 编译器选项不再存在,它生成了更庞大但更适合 IE 的代码。

¥The legacy compiler option, which generated bulkier but IE-friendly code, no longer exists.

编译器选项的更改(Changes to compiler options)

¥Changes to compiler options

  • false / true(之前已弃用)和 "none" 值已从 css 选项中删除,不再作为有效值

    ¥The false / true (already deprecated previously) and the "none" values were removed as valid values from the css option

  • legacy 选项已重新利用

    ¥The legacy option was repurposed

  • hydratable 选项已被删除。Svelte 组件现在始终是可水合的

    ¥The hydratable option has been removed. Svelte components are always hydratable now

  • enableSourcemap 选项已被删除。现在始终会生成源映射,工具可以选择忽略它

    ¥The enableSourcemap option has been removed. Source maps are always generated now, tooling can choose to ignore it

  • tag 选项已被删除。改用组件内部的 <svelte:options customElement="tag-name" />

    ¥The tag option was removed. Use <svelte:options customElement="tag-name" /> inside the component instead

  • loopGuardTimeoutformatsveltePatherrorModevarsReport 选项已被删除

    ¥The loopGuardTimeout, format, sveltePath, errorMode and varsReport options were removed

children 属性是保留的(The children prop is reserved)

¥The children prop is reserved

组件标签内的内容成为名为 children 的片段属性。你不能有同名的单独 prop。

¥Content inside component tags becomes a snippet prop called children. You cannot have a separate prop by that name.

符文模式下的重大更改(Breaking changes in runes mode)

¥Breaking changes in runes mode

某些重大更改仅在你的组件处于符文模式时才适用。

¥Some breaking changes only apply once your component is in runes mode.

不允许绑定到组件导出(Bindings to component exports are not allowed)

¥Bindings to component exports are not allowed

无法直接绑定来自符文模式组件的导出。例如,在组件 A 中拥有 export const foo = ... 然后执行 <A bind:foo /> 会导致错误。改用 bind:this<A bind:this={a} /> — 并以 a.foo 的身份访问导出。此更改使事情更容易推断,因为它强制在 props 和导出之间进行明确分离。

¥Exports from runes mode components cannot be bound to directly. For example, having export const foo = ... in component A and then doing <A bind:foo /> causes an error. Use bind:this instead — <A bind:this={a} /> — and access the export as a.foo. This change makes things easier to reason about, as it enforces a clear separation between props and exports.

需要使用 $bindable() 明确定义绑定(Bindings need to be explicitly defined using $bindable())

¥Bindings need to be explicitly defined using $bindable()

在 Svelte 4 语法中,每个属性(通过 export let 声明)都是可绑定的,这意味着你可以 bind: 到它。在符文模式下,默认情况下属性不可绑定:你需要用 $bindable 符文表示可绑定的属性。

¥In Svelte 4 syntax, every property (declared via export let) is bindable, meaning you can bind: to it. In runes mode, properties are not bindable by default: you need to denote bindable props with the $bindable rune.

如果可绑定属性具有默认值(例如 let { foo = $bindable('bar') } = $props();),则需要将非 undefined 值传递给该属性(如果你要绑定到它)。这可以防止出现模棱两可的行为 - 父级和子级必须具有相同的值 - 并带来更好的性能(在 Svelte 4 中,默认值被反映回父级,导致浪费额外的渲染周期)。

¥If a bindable property has a default value (e.g. let { foo = $bindable('bar') } = $props();), you need to pass a non-undefined value to that property if you’re binding to it. This prevents ambiguous behavior — the parent and child must have the same value — and results in better performance (in Svelte 4, the default value was reflected back to the parent, resulting in wasteful additional render cycles).

accessors 选项被忽略(accessors option is ignored)

¥accessors option is ignored

accessors 选项设置为 true 可使组件的属性在组件实例上直接访问。在符文模式下,组件实例上的属性永远无法访问。如果你需要公开它们,则可以改用组件导出。

¥Setting the accessors option to true makes properties of a component directly accessible on the component instance. In runes mode, properties are never accessible on the component instance. You can use component exports instead if you need to expose them.

immutable 选项被忽略(immutable option is ignored)

¥immutable option is ignored

在符文模式下设置 immutable 选项无效。这个概念被 $state 及其变体的工作方式所取代。

¥Setting the immutable option has no effect in runes mode. This concept is replaced by how $state and its variations work.

类不再是 “auto-reactive”(Classes are no longer “auto-reactive”)

¥Classes are no longer “auto-reactive”

在 Svelte 4 中,执行以下操作会触发反应性:

¥In Svelte 4, doing the following triggered reactivity:

<script>
	let foo = new Foo();
</script>

<button on:click={() => (foo.value = 1)}>{foo.value}</button
>

这是因为 Svelte 编译器将对 foo.value 的分配视为更新引用 foo 的任何内容的指令。在 Svelte 5 中,反应性是在运行时而不是编译时确定的,因此你应该将 value 定义为 Foo 类上的反应性 $state 字段。用 $state(...) 封装 new Foo() 不会产生任何效果 - 只有原始对象和数组才会变得具有深度响应性。

¥This is because the Svelte compiler treated the assignment to foo.value as an instruction to update anything that referenced foo. In Svelte 5, reactivity is determined at runtime rather than compile time, so you should define value as a reactive $state field on the Foo class. Wrapping new Foo() with $state(...) will have no effect — only vanilla objects and arrays are made deeply reactive.

触摸和滚轮事件是被动的(Touch and wheel events are passive)

¥Touch and wheel events are passive

使用 onwheelonmousewheelontouchstartontouchmove 事件属性时,处理程序为 passive,以与浏览器默认值保持一致。通过允许浏览器立即滚动文档,而不是等待查看事件处理程序是否调用 event.preventDefault(),这极大地提高了响应能力。

¥When using onwheel, onmousewheel, ontouchstart and ontouchmove event attributes, the handlers are passive to align with browser defaults. This greatly improves responsiveness by allowing the browser to scroll the document immediately, rather than waiting to see if the event handler calls event.preventDefault().

在极少数情况下,你需要防止这些事件默认值,你应该改用 on(例如在操作内部)。

¥In the very rare cases that you need to prevent these event defaults, you should use on instead (for example inside an action).

属性/prop 语法更严格(Attribute/prop syntax is stricter)

¥Attribute/prop syntax is stricter

在 Svelte 4 中,复杂的属性值不需要引用:

¥In Svelte 4, complex attribute values needn’t be quoted:

<Component prop=this{is}valid />

这是一个被动手段。在符文模式下,如果你想连接内容,则必须将值括在引号中:

¥This is a footgun. In runes mode, if you want to concatenate stuff you must wrap the value in quotes:

<Component prop="this{is}valid" />

请注意,如果你有一个用引号括起来的表达式,例如 Svelte 6 中的 answer="{42}" — Svelte 5 也会发出警告,这将导致值转换为字符串,而不是作为数字传递。

¥Note that Svelte 5 will also warn if you have a single expression wrapped in quotes, like answer="{42}" — in Svelte 6, that will cause the value to be converted to a string, rather than passed as a number.

HTML 结构更严格(HTML structure is stricter)

¥HTML structure is stricter

在 Svelte 4 中,你可以编写 HTML 代码,该代码将在服务器端渲染时由浏览器修复。例如,你可以这样写...

¥In Svelte 4, you were allowed to write HTML code that would be repaired by the browser when server side rendering it. For example you could write this...

<table>
	<tr>
		<td>hi</td>
	</tr>
</table>

...浏览器会自动插入 <tbody> 元素:

¥... and the browser would auto-insert a <tbody> element:

<table>
	<tbody>
		<tr>
			<td>hi</td>
		</tr>
	</tbody>
</table>

Svelte 5 对 HTML 结构的要求更严格,如果浏览器要修复 DOM,它会抛出编译器错误。

¥Svelte 5 is more strict about the HTML structure and will throw a compiler error in cases where the browser would repair the DOM.

其他重大更改(Other breaking changes)

¥Other breaking changes

更严格的 @const 分配验证(Stricter @const assignment validation)

¥Stricter @const assignment validation

不再允许对 @const 声明的解构部分进行赋值。这是一个疏忽,它曾经被允许。

¥Assignments to destructured parts of a @const declaration are no longer allowed. It was an oversight that this was ever allowed.

:is(...) 和 :where(...) 有作用域(:is(...) and :where(...) are scoped)

¥:is(...) and :where(...) are scoped

以前,Svelte 不会分析 :is(...):where(...) 内的选择器,而是将它们有效地视为全局选择器。Svelte 5 在当前组件的上下文中分析它们。因此,如果某些选择器依赖于这种处理,它们现在可能会被视为未使用。要修复此问题,请在 :is(...)/:where(...) 选择器中使用 :global(...)

¥Previously, Svelte did not analyse selectors inside :is(...) and :where(...), effectively treating them as global. Svelte 5 analyses them in the context of the current component. As such, some selectors may now be treated as unused if they were relying on this treatment. To fix this, use :global(...) inside the :is(...)/:where(...) selectors.

使用 Tailwind 的 @apply 指令时,添加 :global 选择器以保留使用 Tailwind 生成的 :is(...) 选择器的规则:

¥When using Tailwind’s @apply directive, add a :global selector to preserve rules that use Tailwind-generated :is(...) selectors:

main :global {
	@apply bg-blue-100 dark:bg-blue-900;
}

CSS 哈希位置不再确定(CSS hash position no longer deterministic)

¥CSS hash position no longer deterministic

以前,Svelte 总是最后插入 CSS 哈希。这在 Svelte 5 中不再有保证。这只有在你 有非常奇怪的 css 选择器 时才会中断。

¥Previously Svelte would always insert the CSS hash last. This is no longer guaranteed in Svelte 5. This is only breaking if you have very weird css selectors.

作用域 CSS 使用 :where(...)(Scoped CSS uses :where(...))

¥Scoped CSS uses :where(...)

为避免由不可预测的特殊性变化引起的问题,范围 CSS 选择器现在使用 :where(.svelte-xyz123) 选择器修饰符和 .svelte-xyz123(其中 xyz123 与以前一样,是 <style> 内容的哈希值)。你可以阅读更多详细信息 此处

¥To avoid issues caused by unpredictable specificity changes, scoped CSS selectors now use :where(.svelte-xyz123) selector modifiers alongside .svelte-xyz123 (where xyz123 is, as previously, a hash of the <style> contents). You can read more detail here.

如果你需要支持未实现 :where 的古老浏览器,你可以手动更改发出的 CSS,但代价是不可预测的特殊性变化:

¥In the event that you need to support ancient browsers that don’t implement :where, you can manually alter the emitted CSS, at the cost of unpredictable specificity changes:

css = css.replace(/:where\((.+?)\)/, '$1');

错误/警告代码已重命名(Error/warning codes have been renamed)

¥Error/warning codes have been renamed

错误和警告代码已重命名。以前他们使用破折号来分隔单词,现在他们使用下划线(例如 foo-bar 变成 foo_bar)。此外,一些代码已略微改写。

¥Error and warning codes have been renamed. Previously they used dashes to separate the words, they now use underscores (e.g. foo-bar becomes foo_bar). Additionally, a handful of codes have been reworded slightly.

减少命名空间数量(Reduced number of namespaces)

¥Reduced number of namespaces

你可以传递给编译器选项 namespace 的有效命名空间数量已减少到 html(默认值)、mathmlsvg

¥The number of valid namespaces you can pass to the compiler option namespace has been reduced to html (the default), mathml and svg.

foreign 命名空间仅适用于 Svelte Native,我们计划在 5.x 次要版本中以不同的方式支持它。

¥The foreign namespace was only useful for Svelte Native, which we’re planning to support differently in a 5.x minor.

beforeUpdate/afterUpdate 更改(beforeUpdate/afterUpdate changes)

¥beforeUpdate/afterUpdate changes

如果 beforeUpdate 修改了模板中引用的变量,则在初始渲染时不再运行两次。

¥beforeUpdate no longer runs twice on initial render if it modifies a variable referenced in the template.

父组件中的 afterUpdate 回调现在将在任何子组件中的 afterUpdate 回调之后运行。

¥afterUpdate callbacks in a parent component will now run after afterUpdate callbacks in any child components.

当组件包含 <slot> 且其内容更新时,beforeUpdate/afterUpdate 不再运行。

¥beforeUpdate/afterUpdate no longer run when the component contains a <slot> and its content is updated.

在符文模式下,这两个函数都是不允许的 - 请改用 $effect.pre(...)$effect(...)

¥Both functions are disallowed in runes mode — use $effect.pre(...) and $effect(...) instead.

contenteditable 行为更改(contenteditable behavior change)

¥contenteditable behavior change

如果你有一个具有相应绑定和其中的反应值(例如:<div contenteditable=true bind:textContent>count is {count}</div>)的 contenteditable 节点,则 contenteditable 中的值将不会通过对 count 的更新进行更新,因为绑定会立即完全控制内容,并且只能通过它进行更新。

¥If you have a contenteditable node with a corresponding binding and a reactive value inside it (example: <div contenteditable=true bind:textContent>count is {count}</div>), then the value inside the contenteditable will not be updated by updates to count because the binding takes full control over the content immediately and it should only be updated through it.

oneventname 属性不再接受字符串值(oneventname attributes no longer accept string values)

¥oneventname attributes no longer accept string values

在 Svelte 4 中,可以将 HTML 元素上的事件属性指定为字符串:

¥In Svelte 4, it was possible to specify event attributes on HTML elements as a string:

<button onclick="alert('hello')">...</button>

不建议这样做,在 Svelte 5 中不再可能,其中 onclick 等属性取代 on:click 作为添加事件处理程序的机制。

¥This is not recommended, and is no longer possible in Svelte 5, where properties like onclick replace on:click as the mechanism for adding event handlers.

null 和 undefined 变为空字符串(null and undefined become the empty string)

¥null and undefined become the empty string

在 Svelte 4 中,nullundefined 被打印为相应的字符串。在 100 个案例中有 99 个你希望它变成空字符串,这也是大多数其他框架所做的。因此,在 Svelte 5 中,nullundefined 变为空字符串。

¥In Svelte 4, null and undefined were printed as the corresponding string. In 99 out of 100 cases you want this to become the empty string instead, which is also what most other frameworks out there do. Therefore, in Svelte 5, null and undefined become the empty string.

bind:files 值只能是 null、undefined 或 FileList(bind:files values can only be null, undefined or FileList)

¥bind:files values can only be null, undefined or FileList

bind:files 现在是双向绑定。因此,设置值时,它需要为假值(nullundefined)或类型为 FileList

¥bind:files is now a two-way binding. As such, when setting a value, it needs to be either falsy (null or undefined) or of type FileList.

绑定现在对表单重置做出反应(Bindings now react to form resets)

¥Bindings now react to form resets

以前,绑定没有考虑表单的 reset 事件,因此值可能与 DOM 不同步。Svelte 5 通过在文档上放置 reset 监听器并在必要时调用绑定来解决这个问题。

¥Previously, bindings did not take into account reset event of forms, and therefore values could get out of sync with the DOM. Svelte 5 fixes this by placing a reset listener on the document and invoking bindings where necessary.

walk 否不再导出(walk no longer exported)

¥walk no longer exported

为方便起见,svelte/compilerestree-walker 重新导出了 walk。这在 Svelte 5 中不再适用,如果需要,请直接从该包中导入它。

¥svelte/compiler reexported walk from estree-walker for convenience. This is no longer true in Svelte 5, import it directly from that package instead in case you need it.

svelte:options 内的内容被禁止(Content inside svelte:options is forbidden)

¥Content inside svelte:options is forbidden

在 Svelte 4 中,你可以在 <svelte:options /> 标签内包含内容。它被忽略了,但你可以在里面写点什么。在 Svelte 5 中,该标签内的内容是编译器错误。

¥In Svelte 4 you could have content inside a <svelte:options /> tag. It was ignored, but you could write something in there. In Svelte 5, content inside that tag is a compiler error.

声明性影子根中的 <slot> 元素被保留(<slot> elements in declarative shadow roots are preserved)

¥<slot> elements in declarative shadow roots are preserved

Svelte 4 用自己的插槽版本替换了所有地方的 <slot /> 标签。如果它们是 <template shadowrootmode="..."> 元素的子元素,Svelte 5 会保留它们。

¥Svelte 4 replaced the <slot /> tag in all places with its own version of slots. Svelte 5 preserves them in the case they are a child of a <template shadowrootmode="..."> element.

<svelte:element> 标签必须是表达式(<svelte:element> tag must be an expression)

¥<svelte:element> tag must be an expression

在 Svelte 4 中,<svelte:element this="div"> 是有效代码。这没有什么意义 - 你应该只做 <div>。在极少数情况下,如果你出于某种原因确实需要使用字面量值,你可以这样做:

¥In Svelte 4, <svelte:element this="div"> is valid code. This makes little sense — you should just do <div>. In the vanishingly rare case that you do need to use a literal value for some reason, you can do this:

<svelte:element this={"div"}>

请注意,虽然 Svelte 4 会将 <svelte:element this="input">(例如)与 <input> 相同地处理,以确定可以应用哪些 bind: 指令,但 Svelte 5 不会。

¥Note that whereas Svelte 4 would treat <svelte:element this="input"> (for example) identically to <input> for the purposes of determining which bind: directives could be applied, Svelte 5 does not.

mount 默认播放过渡(mount plays transitions by default)

¥mount plays transitions by default

用于渲染组件树的 mount 函数默认播放转换,除非 intro 选项设置为 false。这与旧式类组件不同,后者在手动实例化时默认不播放过渡。

¥The mount function used to render a component tree plays transitions by default unless the intro option is set to false. This is different from legacy class components which, when manually instantiated, didn’t play transitions by default.

<img src={...}> 和 {@html ...} 水合不匹配未修复(<img src={...}> and {@html ...} hydration mismatches are not repaired)

¥<img src={...}> and {@html ...} hydration mismatches are not repaired

在 Svelte 4 中,如果 src 属性或 {@html ...} 标签的值在服务器和客户端之间不同(又称水合不匹配),则会修复不匹配。这非常昂贵:设置 src 属性(即使其评估结果相同)会导致重新加载图片和 iframe,并且重新插入大量 HTML 会很慢。

¥In Svelte 4, if the value of a src attribute or {@html ...} tag differ between server and client (a.k.a. a hydration mismatch), the mismatch is repaired. This is very costly: setting a src attribute (even if it evaluates to the same thing) causes images and iframes to be reloaded, and reinserting a large blob of HTML is slow.

由于这些不匹配的情况极为罕见,Svelte 5 假定值保持不变,但在开发过程中,如果值没有改变,则会向你发出警告。要强制更新,你可以执行以下操作:

¥Since these mismatches are extremely rare, Svelte 5 assumes that the values are unchanged, but in development will warn you if they are not. To force an update you can do something like this:

<script>
	let { markup, src } = $props();

	if (typeof window !== 'undefined') {
		// stash the values...
		const initial = { markup, src };

		// unset them...
		markup = src = undefined;

		$effect(() => {
			// ...and reset after we've mounted
			markup = initial.markup;
			src = initial.src;
		});
	}
</script>

{@html markup}
<img {src} />

Hydration 的工作方式不同(Hydration works differently)

¥Hydration works differently

Svelte 5 在服务器端渲染期间使用注释,用于在客户端上实现更强大、更高效的水合。因此,如果你打算对 HTML 输出进行补充,则不应从其中删除注释;如果你手动编写了要由 Svelte 组件补充的 HTML,则需要调整该 HTML 以在正确的位置包含所述注释。

¥Svelte 5 makes use of comments during server side rendering which are used for more robust and efficient hydration on the client. As such, you shouldn’t remove comments from your HTML output if you intend to hydrate it, and if you manually authored HTML to be hydrated by a Svelte component, you need to adjust that HTML to include said comments at the correct positions.

onevent 属性被委托(onevent attributes are delegated)

¥onevent attributes are delegated

事件属性替换事件指令:用 onclick={handler} 代替 on:click={handler}。为了向后兼容,on:event 语法仍然受支持,并且行为与 Svelte 4 中相同。但是,某些 onevent 属性是委托的,这意味着你需要注意不要手动停止这些属性上的事件传播,因为它们可能永远不会到达根目录下此事件类型的监听器。

¥Event attributes replace event directives: Instead of on:click={handler} you write onclick={handler}. For backwards compatibility the on:event syntax is still supported and behaves the same as in Svelte 4. Some of the onevent attributes however are delegated, which means you need to take care to not stop event propagation on those manually, as they then might never reach the listener for this event type at the root.

--style-props 使用不同的元素(--style-props uses a different element)

¥--style-props uses a different element

Svelte 5 使用额外的 <svelte-css-wrapper> 元素而不是 <div> 来封装使用 CSS 自定义属性的组件。

¥Svelte 5 uses an extra <svelte-css-wrapper> element instead of a <div> to wrap the component when using CSS custom properties.