跳至主要內容

Svelte 5 遷移指南

第 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 仍然是數字本身,您可以直接讀取和寫入它,而無需像 .valuegetCount() 這樣的包裝器。

我們為什麼這樣做

在頂層隱式響應式的 let 宣告效果很好,但也意味著響應式受到限制 - 其他任何地方的 let 宣告都不是響應式的。這迫使您在將程式碼從元件頂層重構出來以供重複使用時,必須求助於使用儲存。這意味著您必須學習一個完全不同的響應式模型,並且結果通常不太容易使用。因為在 Svelte 5 中響應式更加明確,所以您可以在元件頂層之外繼續使用相同的 API。前往教學以了解更多資訊。

$: -> $derived/$effect

在 Svelte 4 中,元件頂層的 $: 語句可用於宣告衍生,也就是完全透過其他狀態的計算定義的狀態。在 Svelte 5 中,這是透過使用 $derived 符文來實現的

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

$state 一樣,沒有其他變更。 double 仍然是數字本身,您可以直接讀取它,而無需像 .valuegetDouble() 這樣的包裝器。

$: 語句也可用於建立副作用。在 Svelte 5 中,這是透過使用 $effect 符文來實現的

<script>
	let count = $state(0);
	$:$effect(() => {
		if (count > 5) {
			alert('Count is too high!');
		}
	});
</script>
我們為什麼這樣做

$: 是一個很棒的簡寫,很容易上手:您可以將 $: 放在大多數程式碼的前面,它就會以某種方式運作。這種直觀性也是其缺點,因為您的程式碼越複雜,就越不容易理解。程式碼的意圖是建立衍生還是副作用?透過 $derived$effect,您需要做更多預先的決策 (劇透:90% 的時間您想要 $derived),但是未來的您和團隊中的其他開發人員將會更容易使用。

還有一些很難發現的陷阱

  • $: 只在渲染之前直接更新,這意味著您可以在重新渲染之間讀取過時的值
  • $: 每個滴答僅執行一次,這意味著語句的執行次數可能比您認為的要少
  • $: 依賴項是透過對依賴項的靜態分析來確定的。這在大多數情況下都有效,但在重構期間,依賴項會被移動到函式中而不再可見時,可能會以微妙的方式中斷
  • $: 語句也透過使用對依賴項的靜態分析來排序。在某些情況下,可能會出現並列,並且排序結果會錯誤,需要手動干預。排序也可能會在重構程式碼時中斷,並且某些依賴項會不再可見。

最後,它對 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}>click me</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++}>
	clicks: {count}
</button>

由於它們只是屬性,因此您可以使用正常的簡寫語法...

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

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

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

...儘管在使用命名的事件處理器函式時,通常最好使用更具描述性的名稱。

元件事件

在 Svelte 4 中,元件可以透過使用 createEventDispatcher 建立分發器來發出事件。

此函式在 Svelte 5 中已棄用。相反,元件應接受回呼 prop - 這表示您將函式作為屬性傳遞給這些元件

應用程式
<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>
	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>

冒泡事件

元件不應使用 <button on:click> 從元素「轉發」事件到元件,而是應接受 onclick 回呼 prop

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

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

請注意,這也表示您可以將事件處理器與其他 prop 一起「擴展」到元素上,而無需費力地單獨轉發每個事件

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

<button {...$$props} on:click on:keydown on:all_the_other_stuff {...props}>
	click me
</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>

有三個修飾符 — capturepassivenonpassive — 不能表示為包裝函式,因為它們需要在繫結事件處理器時而不是在執行時套用。

對於 capture,我們將修飾符新增至事件名稱

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

同時,變更事件處理器的 passive 選項不是一件輕易的事情。如果您有使用案例,那麼您可能不會!— 那麼您將需要使用動作來自行套用事件處理器。

多個事件處理器

在 Svelte 4 中,這是可能的

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

不允許元素上的重複屬性/prop (現在包括事件處理器)。請改為執行此操作

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

當擴展 prop 時,本機事件處理器必須在擴展之後,否則它們可能會被覆蓋

<button
	{...props}
	onclick={(e) => {
		doStuff(e);
		props.onclick?.(e);
	}}
>
	...
</button>
我們為什麼這樣做

createEventDispatcher 一直有點樣板式

  • 匯入函式
  • 呼叫函式以取得分發函式
  • 呼叫指定的派發函式,並傳遞一個字串和可能有的酬載。
  • 在另一端透過 .detail 屬性來擷取該酬載,因為事件本身總是 CustomEvent

過去總是可以使用元件回呼 props,但因為必須使用 on: 來監聽 DOM 事件,所以為了語法一致性,使用 createEventDispatcher 來處理元件事件是合理的。現在我們有了事件屬性 (onclick),情況則相反:回呼 props 現在是更合理的做法。

移除事件修飾符可以說是讓喜歡事件修飾符簡寫語法的人覺得是退步的改變之一。考量到它們的使用頻率不高,我們犧牲較小的介面範圍,換取更多的明確性。修飾符也前後不一致,因為它們大多數只能在 DOM 元素上使用。

同一個事件的多個監聽器也不再可行,但這無論如何都是一種反模式,因為它會阻礙可讀性:如果有很多屬性,除非它們彼此相鄰,否則會很難發現有兩個處理函式。它也暗示這兩個處理函式是獨立的,但實際上,像是 one 裡面的 event.stopImmediatePropagation() 就會阻止 two 被呼叫。

藉由棄用 createEventDispatcheron: 指令,改用回呼 props 和一般的元素屬性,我們

  • 降低了 Svelte 的學習曲線
  • 移除了樣板程式碼,特別是關於 createEventDispatcher 的部分
  • 移除了為可能沒有監聽器的事件建立 CustomEvent 物件的開銷
  • 新增了散佈事件處理函式的功能
  • 新增了知道哪個事件處理函式被提供給元件的功能
  • 新增了表達給定的事件處理函式是必要還是可選的功能
  • 提高了類型安全性 (先前,Svelte 實際上不可能保證元件不會發出特定的事件)

片段 (Snippets) 取代插槽 (Slots)

在 Svelte 4 中,可以使用插槽將內容傳遞給元件。Svelte 5 用功能更強大、更具彈性的片段來取代它們,因此 Svelte 5 中已棄用插槽。

不過,它們仍然可以運作,你可以在元件中混用片段和插槽。

當使用自訂元素時,你仍然應該像以前一樣使用 <slot />。在未來的版本中,當 Svelte 移除其內部的插槽版本時,它會將這些插槽保持原樣,也就是輸出一個普通的 DOM 標籤,而不是轉換它。

預設內容

在 Svelte 4 中,將 UI 片段傳遞給子元件最簡單的方式是使用 <slot />。在 Svelte 5 中,則改用 children prop,然後使用 {@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">No items yet</span>
	{#snippet empty()}
		<span>No items yet</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}
我們為什麼這樣做

插槽很容易上手,但使用案例越進階,語法就變得越複雜和令人困惑

  • let: 語法對很多人來說很令人困惑,因為它會建立一個變數,而所有其他 : 指令都是接收一個變數
  • 使用 let: 宣告的變數範圍不明確。在上面的範例中,看起來你可以在 empty 插槽中使用 item 插槽 prop,但事實並非如此。
  • 具名插槽必須使用 slot 屬性套用至元素。有時你不想建立元素,所以我們必須加入 <svelte:fragment> API。
  • 具名插槽也可以套用至元件,這會改變 let: 指令可用的語義 (即使到今天,我們這些維護者也常常不知道它是怎麼運作的)。

片段透過更具可讀性和清晰度來解決所有這些問題。同時,它們功能更強大,因為它們允許你定義可以在任何地方呈現的 UI 區塊,而不僅僅是將它們作為 props 傳遞給元件。

遷移腳本

到目前為止,你應該對遷移前後的情況以及舊語法與新語法的關聯有相當好的了解。你可能也清楚意識到,很多遷移過程都相當技術性和重複性高 - 這是你不希望手動完成的事情。

我們也這麼認為,這就是為什麼我們提供遷移腳本來自動執行大部分的遷移。你可以使用 npx sv migrate svelte-5 來升級你的專案。這會執行下列事項

  • 在你的 package.json 中更新核心相依性
  • 遷移至 runes (let -> $state 等)
  • 將 DOM 元素的事件指令遷移至事件屬性 (on:click -> onclick)
  • 將插槽建立遷移至 render 標籤 (<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 則不會,因此將其轉換為 $effect 並不安全。相反地,使用 run 作為權宜之計。run 模仿了 $: 的大部分特性,它會在伺服器上執行一次,並在客戶端上作為 $effect.pre 執行 ($effect.pre 在變更套用至 DOM之前執行;你很可能想改用 $effect)。

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

事件修飾符

事件修飾符不適用於事件屬性 (例如,你無法執行 onclick|preventDefault={...})。因此,當將事件指令遷移至事件屬性時,我們需要這些修飾符的函式取代方案。這些是從 svelte/legacy 匯入的,應該遷移出去,改為使用像是 event.preventDefault()

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

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

未自動遷移的事項

遷移腳本不會轉換 createEventDispatcher。你需要手動調整那些部分。它不這麼做的原因是風險太高,因為這可能會導致元件的使用者發生錯誤,而遷移腳本無法找出這些錯誤。

遷移腳本不會轉換 beforeUpdate/afterUpdate。它不這麼做的原因是無法判斷程式碼的實際意圖。根據經驗法則,你通常可以使用 $effect.pre (與 beforeUpdate 執行的時間相同) 和 tick (從 svelte 匯入,讓你等待變更套用至 DOM 後再執行某些工作) 的組合。

元件不再是類別

在 Svelte 3 和 4 中,元件是類別。在 Svelte 5 中,它們是函式,應該以不同的方式實例化。如果你需要手動實例化元件,則應該改用 mounthydrate (從 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.

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 方法。這些是它們的取代方案。

對於 $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.

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 — 相反地,請使用回呼

對於 $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

Declares reactive state.

Example:

let count = $state(0);

https://svelte.dev.org.tw/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

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>): void

Unmounts a component that was previously mounted using mount or hydrate.

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>): void

Unmounts a component that was previously mounted using mount or hydrate.

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。

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 方法。

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

請注意,mounthydrate 並非同步的,因此像是 onMount 之類的回呼函式在函式返回時並不會被呼叫,且待處理的 promise 區塊也尚未渲染(因為 #await 會等待一個 microtask 以等待可能立即解析的 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.

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 中,預設情況下不再如此,因為大多數情況下,您使用的是以其他方式處理 CSS 的工具鏈(例如 SvelteKit)。如果您需要從 render 返回 CSS,您可以將 css 編譯器選項設定為 'injected',它會將 <style> 元素新增到 head 中。

元件型別變更

從類別轉向函式的變更也反映在型別宣告中:Svelte 4 中的基底類別 SvelteComponent 已被棄用,取而代之的是新的 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&#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; }>;

若要宣告需要特定型別的元件

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

兩個工具型別 ComponentEventsComponentType 也被棄用。 ComponentEvents 已過時,因為事件現在被定義為回呼 prop,而 ComponentType 已過時,因為新的 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 標籤內的空白。

與之前一樣,您可以透過在編譯器設定中或在每個元件的 <svelte:options> 中設定 preserveWhitespace 選項來停用空白修剪。

需要現代瀏覽器

Svelte 5 由於各種原因需要現代瀏覽器(換句話說,不是 Internet Explorer):

  • 它使用 Proxies
  • 具有 clientWidth / clientHeight/offsetWidth/offsetHeight 綁定的元素使用 ResizeObserver,而不是複雜的 <iframe> 技巧。
  • <input type="range" bind:value={...} /> 僅使用 input 事件監聽器,而不是像備用方案一樣也監聽 change 事件。

產生較龐大但與 IE 相容程式碼的 legacy 編譯器選項已不再存在。

編譯器選項的變更

  • css 選項中移除了 false / true (先前已棄用) 和 "none" 值,作為有效值。
  • legacy 選項已被重新利用。
  • hydratable 選項已被移除。Svelte 元件現在總是可水合的。
  • enableSourcemap 選項已被移除。現在總是會產生原始碼對應,工具可以選擇忽略它。
  • tag 選項已被移除。請改為在元件內部使用 <svelte:options customElement="tag-name" />
  • loopGuardTimeoutformatsveltePatherrorModevarsReport 選項已被移除。

children prop 是保留的

元件標籤內的內容會變成一個名為 children 的程式碼片段 prop。您不能有另一個同名的 prop。

點表示法表示元件

在 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。此變更使事情更容易理解,因為它強制區分了 prop 和導出之間有明確的分隔。

需要使用 $bindable() 明確定義綁定

在 Svelte 4 語法中,每個屬性 (透過 export let 宣告) 都是可綁定的,這表示您可以 bind: 到它。在符文模式中,屬性預設不可綁定:您需要使用 $bindable 符號來表示可綁定的 prop。

如果可綁定的屬性具有預設值 (例如 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 欄位。用 $state(...) 包裝 new Foo() 不會有任何效果 — 只有 vanilla 物件和陣列才能進行深度反應式處理。

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

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

觸控和滾輪事件是被動的

當使用 onwheelonmousewheelontouchstartontouchmove 事件屬性時,處理常式是被動的,以與瀏覽器預設值對齊。這可以讓瀏覽器立即捲動文件,而無需等待查看事件處理常式是否呼叫 event.preventDefault(),從而大幅提高反應速度。

在您需要防止這些事件預設值的極少數情況下,您應該改用 on (例如在動作內部)。

屬性/prop 語法更嚴格

在 Svelte 4 中,複雜的屬性值不需要加上引號。

<Component prop=this{is}valid />

這是一個潛在的陷阱。在符文模式中,如果要串接內容,則必須將值包裝在引號中。

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

請注意,如果您有包裝在引號中的單一表示式 (例如 answer="{42}"),Svelte 5 也會發出警告 — 在 Svelte 6 中,這會導致值轉換為字串,而不是以數字傳遞。

HTML 結構更嚴格

在 Svelte 4 中,您允許撰寫會在伺服器端渲染時由瀏覽器修復的 HTML 程式碼。例如,您可以撰寫此程式碼...

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

...瀏覽器會自動插入 <tbody> 元素。

<table>
	<tbody>
		<tr>
			<td>hi</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 選擇器現在使用 :where(.svelte-xyz123) 選擇器修飾符以及 .svelte-xyz123(其中 xyz123 與之前一樣是 <style> 內容的雜湊值)。您可以這裡閱讀更多詳細資訊。

如果需要支援不實作 :where 的舊版瀏覽器,您可以手動修改發出的 CSS,但這會導致權重變更難以預測。

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

錯誤/警告代碼已重新命名

錯誤和警告代碼已重新命名。之前它們使用破折號分隔單字,現在則使用底線(例如 foo-bar 變成 foo_bar)。此外,少數代碼的措辭也稍作修改。

減少命名空間數量

您可以傳遞給編譯器選項 namespace 的有效命名空間數量已減少為 html(預設值)、mathmlsvg

foreign 命名空間僅對 Svelte Native 有用,我們計劃在 5.x 次要版本中以不同的方式支援它。

beforeUpdate/afterUpdate 變更

如果 beforeUpdate 修改了模板中引用的變數,則它在初始渲染時不再執行兩次。

父元件中的 afterUpdate 回呼現在將在任何子元件中的 afterUpdate 回呼之後執行。

這兩個函數在 runes 模式中都不允許使用,請改用 $effect.pre(...)$effect(...)

contenteditable 行為變更

如果您有一個 contenteditable 節點,並且它有對應的繫結以及裡面有一個響應式值(範例:<div contenteditable=true bind:textContent>count is {count}</div>),則 contenteditable 裡面的值將不會因 count 的更新而更新,因為繫結會立即完全控制內容,並且應該只透過它來更新。

oneventname 屬性不再接受字串值

在 Svelte 4 中,可以在 HTML 元素上將事件屬性指定為字串。

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

不建議這樣做,而且在 Svelte 5 中不再可能,其中類似 onclick 的屬性會取代 on:click 作為新增事件處理常式的機制。

null 和 undefined 變成空字串

在 Svelte 4 中,nullundefined 會印出對應的字串。在 99% 的情況下,您希望它變成空字串,這也是其他大多數框架的做法。因此,在 Svelte 5 中,nullundefined 會變成空字串。

bind:files 值只能是 null、undefined 或 FileList

bind:files 現在是雙向繫結。因此,設定值時,它必須是假值 (nullundefined) 或 FileList 類型。

繫結現在會對表單重設做出反應

之前,繫結沒有考慮到表單的 reset 事件,因此值可能會與 DOM 不同步。Svelte 5 透過在文件上放置 reset 接聽器並在必要時叫用繫結來修復此問題。

walk 不再匯出

svelte/compiler 為了方便起見,從 estree-walker 重新匯出了 walk。在 Svelte 5 中不再如此,如果您需要它,請改為直接從該套件匯入。

svelte:options 內的內容被禁止

在 Svelte 4 中,您可以在 <svelte:options /> 標籤內使用內容。它會被忽略,但您可以在裡面寫一些東西。在 Svelte 5 中,該標籤內的內容會產生編譯器錯誤。

宣告式陰影根中的 <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') {
		// 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} />

水合作用的運作方式不同

Svelte 5 在伺服器端渲染期間使用註解,這些註解用於在用戶端上進行更穩健有效率的水合作用。因此,如果您打算對其進行水合作用,則不應從 HTML 輸出中移除註解,而且如果您手動編寫要由 Svelte 元件水合的 HTML,則需要調整該 HTML 以在正確的位置包含這些註解。

onevent 屬性會被委派

事件屬性會取代事件指令:您應該寫入 onclick={handler} 而不是 on:click={handler}。為了向後相容,仍然支援 on:event 語法,其行為與 Svelte 4 中的相同。但是,有些 onevent 屬性會被委派,這表示您需要小心,不要在那些屬性上手動停止事件傳播,因為它們可能永遠不會到達根目錄中此事件類型的接聽器。

--style-props 使用不同的元素

Svelte 5 在使用 CSS 自訂屬性時,會使用額外的 <svelte-css-wrapper> 元素而不是 <div> 來包裝元件。

在 GitHub 上編輯此頁面