前言
Svelte,一個語法簡潔、入門容易,面向未來的前端框架。
從 Svelte 誕生之初,就備受開發者的喜愛,根據統計,從 2019 年到 2024 年,連續 6 年一直是開發者最感興趣的前端框架 No.1:
Svelte 以其獨特的編譯時優化機制著稱,具有輕量級、高性能、易上手等特性,非常適合構建輕量級 Web 項目。
為了幫助大家學習 Svelte,我同時搭建了 Svelte 最新的中文文檔站點。
如果需要進階學習,也可以入手我的小冊《Svelte 開發指南》,語法篇、實戰篇、原理篇三大篇章帶你系統掌握 Svelte!
歡迎圍觀我的“網頁版朋友圈”、加入“冴羽·成長陪伴社群”,踏上“前端大佬成長之路”。
高級路由
剩余參數
如果路由段的數量未知,您可以使用剩余語法 — 例如您可以像這樣實現 GitHub 的文件查看器…
/[org]/[repo]/tree/[branch]/[...file]
…在這種情況下,對 /sveltejs/kit/tree/main/documentation/docs/04-advanced-routing.md
的請求將導致以下參數可供頁面使用:
// @noErrors
{org: 'sveltejs',repo: 'kit',branch: 'main',file: 'documentation/docs/04-advanced-routing.md'
}
[!NOTE]
src/routes/a/[...rest]/z/+page.svelte
將匹配/a/z
(即完全沒有參數)以及/a/b/z
和/a/b/c/z
等。請確保檢查剩余參數的值是否有效,例如使用匹配器。
404 頁面
剩余參數還允許您渲染自定義 404。給定這些路由…
src/routes/
├ marx-brothers/
│ ├ chico/
│ ├ harpo/
│ ├ groucho/
│ └ +error.svelte
└ +error.svelte
…如果您訪問 /marx-brothers/karl
,marx-brothers/+error.svelte
文件將不會被渲染,因為沒有匹配到路由。如果您想渲染嵌套的錯誤頁面,您應該創建一個匹配任何 /marx-brothers/*
請求的路由,并從中返回 404:
src/routes/
├ marx-brothers/
+++| ├ [...path]/+++
│ ├ chico/
│ ├ harpo/
│ ├ groucho/
│ └ +error.svelte
└ +error.svelte
/// file: src/routes/marx-brothers/[...path]/+page.js
import { error } from '@sveltejs/kit';/** @type {import('./$types').PageLoad} */
export function load(event) {error(404, 'Not Found');
}
[!NOTE] 如果您不處理 404 情況,它們將出現在
handleError
中
可選參數
像 [lang]/home
這樣的路由包含一個名為 lang
的必需參數。有時需要讓這些參數成為可選的,這樣在這個例子中 home
和 en/home
都指向同一個頁面。您可以通過將參數包裹在另一對括號中來實現:[[lang]]/home
注意,可選路由參數不能跟在剩余參數后面([...rest]/[[optional]]
),因為參數是"貪婪"匹配的,可選參數永遠不會被使用。
匹配
像 src/routes/fruits/[page]
這樣的路由會匹配 /fruits/apple
,但它也會匹配 /fruits/rocketship
。我們不希望這樣。您可以通過在 params
目錄中添加一個 匹配器 — 它接收參數字符串("apple"
或 "rocketship"
)并在有效時返回 true
— 來確保路由參數格式正確…
/// file: src/params/fruit.js
/*** @param {string} param* @return {param is ('apple' | 'orange')}* @satisfies {import('@sveltejs/kit').ParamMatcher}*/
export function match(param) {return param === 'apple' || param === 'orange';
}
…并增強您的路由:
src/routes/fruits/[page+++=fruit+++]
如果路徑名不匹配,SvelteKit 將嘗試匹配其他路由(使用下面指定的排序順序),最終返回 404。
params
目錄中的每個模塊對應一個匹配器,除了 *.test.js
和 *.spec.js
文件,這些文件可用于對匹配器進行單元測試。
[!NOTE] 匹配器在服務端和瀏覽器中都會運行。
排序
多個路由可能匹配同一個路徑。例如,以下每個路由都會匹配 /foo-abc
:
src/routes/[...catchall]/+page.svelte
src/routes/[[a=x]]/+page.svelte
src/routes/[b]/+page.svelte
src/routes/foo-[c]/+page.svelte
src/routes/foo-abc/+page.svelte
SvelteKit 需要知道正在請求哪個路由。為此,它根據以下規則進行排序…
- 更具體的路由具有更高的優先級(例如,沒有參數的路由比有一個動態參數的路由更具體,以此類推)
- 帶有匹配器的參數(
[name=type]
)比沒有匹配器的參數([name]
)具有更高的優先級 [[optional]]
和[...rest]
參數將被忽略,除非它們是路由的最后一部分,在這種情況下它們被視為最低優先級。換句話說,x/[[y]]/z
在排序時被視為與x/z
等同- 平局通過字母順序解決
…導致此排序結果,這意味著 /foo-abc
將調用 src/routes/foo-abc/+page.svelte
,而 /foo-def
將調用 src/routes/foo-[c]/+page.svelte
而不是不太具體的路由:
src/routes/foo-abc/+page.svelte
src/routes/foo-[c]/+page.svelte
src/routes/[[a=x]]/+page.svelte
src/routes/[b]/+page.svelte
src/routes/[...catchall]/+page.svelte
編碼
某些字符不能用于文件系統 — Linux 和 Mac 上的 /
,Windows 上的 \ / : * ? " < > |
。#
和 %
字符在 URL 中有特殊含義,[ ] ( )
字符在 SvelteKit 中有特殊含義,所以這些字符也不能直接用作路由的一部分。
如果要在路由中使用這些字符,您可以使用十六進制轉義序列,其格式為 [x+nn]
,其中 nn
是十六進制字符代碼:
\
—[x+5c]
/
—[x+2f]
:
—[x+3a]
*
—[x+2a]
?
—[x+3f]
"
—[x+22]
<
—[x+3c]
>
—[x+3e]
|
—[x+7c]
#
—[x+23]
%
—[x+25]
[
—[x+5b]
]
—[x+5d]
(
—[x+28]
)
—[x+29]
例如,要創建一個 /smileys/:-)
路由,您需要創建一個 src/routes/smileys/[x+3a]-[x+29]/+page.svelte
文件。
您可以使用 JavaScript 確定字符的十六進制代碼:
':'.charCodeAt(0).toString(16); // '3a',因此使用 '[x+3a]'
您也可以使用 Unicode 轉義序列。通常您不需要這樣做,因為您可以直接使用未編碼的字符,但如果出于某種原因您不能有包含例如表情符號的文件名,那么您可以使用轉義字符。換句話說,這些是等效的:
src/routes/[u+d83e][u+dd2a]/+page.svelte
src/routes/🤪/+page.svelte
Unicode 轉義序列的格式是 [u+nnnn]
,其中 nnnn
是介于 0000
和 10ffff
之間的有效值。(與 JavaScript 字符串轉義不同,不需要使用代理對來表示超過 ffff
的代碼點。)要了解更多關于 Unicode 編碼的信息,請參考 使用 Unicode 編程。
[!NOTE] 由于 TypeScript 難以處理以
.
字符開頭的目錄,在創建例如.well-known
路由時,您可能會發現對這些字符進行編碼會很有用:src/routes/[x+2e]well-known/...
高級布局
默認情況下,布局層次結構 反映了 路由層次結構。在某些情況下,這可能不是您想要的。
(group)
也許您有一些路由是"應用"路由,應該有一個布局(例如 /dashboard
或 /item
),而其他路由則是"營銷"路由,應該有一個不同的布局(/about
或 /testimonials
)。
我們可以用一個括號括起來的目錄名稱對這些路由進行分組 — 與普通目錄不同,(app)
和 (marketing)
不影響路由的 URL 路徑名:
src/routes/
+++│ (app)/+++
│ ├ dashboard/
│ ├ item/
│ └ +layout.svelte
+++│ (marketing)/+++
│ ├ about/
│ ├ testimonials/
│ └ +layout.svelte
├ admin/
└ +layout.svelte
您也可以直接將一個 +page
放在 (group)
中,例如如果 /
應該是一個 (app)
或 (marketing)
頁面。
跳出布局
根布局適用于您的應用的每個頁面 — 如果省略,它默認為 {@render children()}
。如果您希望某些頁面有不同于其余頁面的布局層次結構,那么您可以將整個應用放在一個或多個組內,除了 不應繼承公共布局的路由。
在上面的例子中,/admin
路由不繼承 (app)
或 (marketing)
布局。
+page@
頁面可以在逐個路由的基礎上跳出當前布局層次結構。假設我們在前面例子的 (app)
組內有一個 /item/[id]/embed
路由:
src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
+++│ │ │ │ └ +page.svelte+++
│ │ │ └ +layout.svelte
│ │ └ +layout.svelte
│ └ +layout.svelte
└ +layout.svelte
通常,這將繼承根布局、(app)
布局、item
布局和 [id]
布局。我們可以通過附加 @
后跟路由段名來重置到其中一個布局 — 對于根布局,使用空字符串。在此例子中,我們可以選擇以下選項:
+page@[id].svelte
- 繼承自src/routes/(app)/item/[id]/+layout.svelte
+page@item.svelte
- 繼承自src/routes/(app)/item/+layout.svelte
+page@(app).svelte
- 繼承自src/routes/(app)/+layout.svelte
+page@.svelte
- 繼承自src/routes/+layout.svelte
src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
+++│ │ │ │ └ +page@(app).svelte+++
│ │ │ └ +layout.svelte
│ │ └ +layout.svelte
│ └ +layout.svelte
└ +layout.svelte
+layout@
像頁面一樣,布局本身也可以使用相同的方式跳出其父布局層次結構。例如,+layout@.svelte
組件將將重置其所有子路由的層次結構。
src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
│ │ │ │ └ +page.svelte // 使用 (app)/item/[id]/+layout.svelte
│ │ │ ├ +layout.svelte // 繼承自 (app)/item/+layout@.svelte
│ │ │ └ +page.svelte // 使用 (app)/item/+layout@.svelte
│ │ └ +layout@.svelte // 繼承自根布局,跳過 (app)/+layout.svelte
│ └ +layout.svelte
└ +layout.svelte
何時使用布局分組
并非所有用例都適合使用布局分組,您也不應該覺得必須使用它們。可能您的用例會導致復雜的 (group)
嵌套,或者您不想為一個特例引入 (group)
。使用其他方式如組合(可復用的 load
函數或 Svelte 組件)或 if 語句來實現您想要的效果也完全可以。以下示例展示了一個回退到根布局并復用其他布局也可以使用的組件和函數:
<!--- file: src/routes/nested/route/+layout@.svelte --->
<script>import ReusableLayout from '$lib/ReusableLayout.svelte';let { data, children } = $props();
</script><ReusableLayout {data}>{@render children()}
</ReusableLayout>
/// file: src/routes/nested/route/+layout.js
// @filename: ambient.d.ts
declare module "$lib/reusable-load-function" {export function reusableLoad(event: import('@sveltejs/kit').LoadEvent): Promise<Record<string, any>>;
}
// @filename: index.js
// ---cut---
import { reusableLoad } from '$lib/reusable-load-function';/** @type {import('./$types').PageLoad} */
export function load(event) {// Add additional logic here, if neededreturn reusableLoad(event);
}
進一步閱讀
- 教程:高級路由
Svelte 中文文檔
點擊查看中文文檔:SvelteKit 高級路由
系統學習 Svelte,歡迎入手小冊《Svelte 開發指南》。語法篇、實戰篇、原理篇三大篇章帶你系統掌握 Svelte!
此外我還寫過 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答讀者問等 14 個系列文章, 全系列文章目錄:https://github.com/mqyqingfeng/Blog
歡迎圍觀我的“網頁版朋友圈”、加入“冴羽·成長陪伴社群”,踏上“前端大佬成長之路”。