SvelteKit 最新中文文档教程(12)—— 高级路由
<h2 id="前言">前言</h2><p>Svelte,一个语法简洁、入门容易,面向未来的前端框架。</p>
<p>从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,<strong>从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1</strong>:</p>
<p><img src="https://yayujs-blog.oss-cn-beijing.aliyuncs.com/405488775-48df16b1-939c-489b-8d52-6071869893f0.png"></p>
<p>Svelte 以其独特的编译时优化机制著称,具有<strong>轻量级</strong>、<strong>高性能</strong>、<strong>易上手</strong>等特性,<strong>非常适合构建轻量级 Web 项目</strong>。</p>
<p>为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。</p>
<p>如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!</p>
<p>欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。</p>
<h2 id="高级路由">高级路由</h2>
<h2 id="剩余参数">剩余参数</h2>
<p>如果路由段的数量未知,您可以使用剩余语法 — 例如您可以像这样实现 GitHub 的文件查看器...</p>
<pre><code class="language-bash">///tree//[...file]
</code></pre>
<p>...在这种情况下,对 <code>/sveltejs/kit/tree/main/documentation/docs/04-advanced-routing.md</code> 的请求将导致以下参数可供页面使用:</p>
<pre><code class="language-js">// @noErrors
{
org: 'sveltejs',
repo: 'kit',
branch: 'main',
file: 'documentation/docs/04-advanced-routing.md'
}
</code></pre>
<blockquote>
<p>[!NOTE] <code>src/routes/a/[...rest]/z/+page.svelte</code> 将匹配 <code>/a/z</code>(即完全没有参数)以及 <code>/a/b/z</code> 和 <code>/a/b/c/z</code> 等。请确保检查剩余参数的值是否有效,例如使用匹配器。</p>
</blockquote>
<h3 id="404-页面">404 页面</h3>
<p>剩余参数还允许您渲染自定义 404。给定这些路由...</p>
<pre><code class="language-tree">src/routes/
├ marx-brothers/
│ ├ chico/
│ ├ harpo/
│ ├ groucho/
│ └ +error.svelte
└ +error.svelte
</code></pre>
<p>...如果您访问 <code>/marx-brothers/karl</code>,<code>marx-brothers/+error.svelte</code> 文件将不会被渲染,因为没有匹配到路由。如果您想渲染嵌套的错误页面,您应该创建一个匹配任何 <code>/marx-brothers/*</code> 请求的路由,并从中返回 404:</p>
<pre><code class="language-tree">src/routes/
├ marx-brothers/
+++| ├ [...path]/+++
│ ├ chico/
│ ├ harpo/
│ ├ groucho/
│ └ +error.svelte
└ +error.svelte
</code></pre>
<pre><code class="language-js">/// 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');
}
</code></pre>
<blockquote>
<p>[!NOTE] 如果您不处理 404 情况,它们将出现在 <code>handleError</code> 中</p>
</blockquote>
<h2 id="可选参数">可选参数</h2>
<p>像 <code>/home</code> 这样的路由包含一个名为 <code>lang</code> 的必需参数。有时需要让这些参数成为可选的,这样在这个例子中 <code>home</code> 和 <code>en/home</code> 都指向同一个页面。您可以通过将参数包裹在另一对括号中来实现:<code>[]/home</code></p>
<p>注意,可选路由参数不能跟在剩余参数后面(<code>[...rest]/[]</code>),因为参数是"贪婪"匹配的,可选参数永远不会被使用。</p>
<h2 id="匹配">匹配</h2>
<p>像 <code>src/routes/fruits/</code> 这样的路由会匹配 <code>/fruits/apple</code>,但它也会匹配 <code>/fruits/rocketship</code>。我们不希望这样。您可以通过在 <code>params</code> 目录中添加一个 <em>匹配器</em> — 它接收参数字符串(<code>"apple"</code> 或 <code>"rocketship"</code>)并在有效时返回 <code>true</code> — 来确保路由参数格式正确...</p>
<pre><code class="language-js">/// 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';
}
</code></pre>
<p>...并增强您的路由:</p>
<pre><code>src/routes/fruits/
</code></pre>
<p>如果路径名不匹配,SvelteKit 将尝试匹配其他路由(使用下面指定的排序顺序),最终返回 404。</p>
<p><code>params</code> 目录中的每个模块对应一个匹配器,除了 <code>*.test.js</code> 和 <code>*.spec.js</code> 文件,这些文件可用于对匹配器进行单元测试。</p>
<blockquote>
<p>[!NOTE] 匹配器在服务端和浏览器中都会运行。</p>
</blockquote>
<h2 id="排序">排序</h2>
<p>多个路由可能匹配同一个路径。例如,以下每个路由都会匹配 <code>/foo-abc</code>:</p>
<pre><code class="language-bash">src/routes/[...catchall]/+page.svelte
src/routes/[]/+page.svelte
src/routes//+page.svelte
src/routes/foo-/+page.svelte
src/routes/foo-abc/+page.svelte
</code></pre>
<p>SvelteKit 需要知道正在请求哪个路由。为此,它根据以下规则进行排序...</p>
<ul>
<li>更具体的路由具有更高的优先级(例如,没有参数的路由比有一个动态参数的路由更具体,以此类推)</li>
<li>带有匹配器的参数(<code></code>)比没有匹配器的参数(<code></code>)具有更高的优先级</li>
<li><code>[]</code> 和 <code>[...rest]</code> 参数将被忽略,除非它们是路由的最后一部分,在这种情况下它们被视为最低优先级。换句话说,<code>x/[]/z</code> 在排序时被视为与 <code>x/z</code> 等同</li>
<li>平局通过字母顺序解决</li>
</ul>
<p>...导致此排序结果,这意味着 <code>/foo-abc</code> 将调用 <code>src/routes/foo-abc/+page.svelte</code>,而 <code>/foo-def</code> 将调用 <code>src/routes/foo-/+page.svelte</code> 而不是不太具体的路由:</p>
<pre><code class="language-bash">src/routes/foo-abc/+page.svelte
src/routes/foo-/+page.svelte
src/routes/[]/+page.svelte
src/routes//+page.svelte
src/routes/[...catchall]/+page.svelte
</code></pre>
<h2 id="编码">编码</h2>
<p>某些字符不能用于文件系统 — Linux 和 Mac 上的 <code>/</code>,Windows 上的 <code>\ / : * ? " < > |</code>。<code>#</code> 和 <code>%</code> 字符在 URL 中有特殊含义,<code>[ ] ( )</code> 字符在 SvelteKit 中有特殊含义,所以这些字符也不能直接用作路由的一部分。</p>
<p>如果要在路由中使用这些字符,您可以使用十六进制转义序列,其格式为 <code></code>,其中 <code>nn</code> 是十六进制字符代码:</p>
<ul>
<li><code>\</code> — <code></code></li>
<li><code>/</code> — <code></code></li>
<li><code>:</code> — <code></code></li>
<li><code>*</code> — <code></code></li>
<li><code>?</code> — <code></code></li>
<li><code>"</code> — <code></code></li>
<li><code><</code> — <code></code></li>
<li><code>></code> — <code></code></li>
<li><code>|</code> — <code></code></li>
<li><code>#</code> — <code></code></li>
<li><code>%</code> — <code></code></li>
<li><code>[</code> — <code></code></li>
<li><code>]</code> — <code></code></li>
<li><code>(</code> — <code></code></li>
<li><code>)</code> — <code></code></li>
</ul>
<p>例如,要创建一个 <code>/smileys/:-)</code> 路由,您需要创建一个 <code>src/routes/smileys/-/+page.svelte</code> 文件。</p>
<p>您可以使用 JavaScript 确定字符的十六进制代码:</p>
<pre><code class="language-js">':'.charCodeAt(0).toString(16); // '3a',因此使用 ''
</code></pre>
<p>您也可以使用 Unicode 转义序列。通常您不需要这样做,因为您可以直接使用未编码的字符,但如果出于某种原因您不能有包含例如表情符号的文件名,那么您可以使用转义字符。换句话说,这些是等效的:</p>
<pre><code>src/routes//+page.svelte
src/routes/🤪/+page.svelte
</code></pre>
<p>Unicode 转义序列的格式是 <code></code>,其中 <code>nnnn</code> 是介于 <code>0000</code> 和 <code>10ffff</code> 之间的有效值。(与 JavaScript 字符串转义不同,不需要使用代理对来表示超过 <code>ffff</code> 的代码点。)要了解更多关于 Unicode 编码的信息,请参考 使用 Unicode 编程。</p>
<blockquote>
<p>[!NOTE] 由于 TypeScript 难以处理以 <code>.</code> 字符开头的目录,在创建例如 <code>.well-known</code> 路由时,您可能会发现对这些字符进行编码会很有用:<code>src/routes/well-known/...</code></p>
</blockquote>
<h2 id="高级布局">高级布局</h2>
<p>默认情况下,<em>布局层次结构</em> 反映了 <em>路由层次结构</em>。在某些情况下,这可能不是您想要的。</p>
<h3 id="group">(group)</h3>
<p>也许您有一些路由是"应用"路由,应该有一个布局(例如 <code>/dashboard</code> 或 <code>/item</code>),而其他路由则是"营销"路由,应该有一个不同的布局(<code>/about</code> 或 <code>/testimonials</code>)。</p>
<p>我们可以用一个括号括起来的目录名称对这些路由进行分组 — 与普通目录不同,<code>(app)</code> 和 <code>(marketing)</code> 不影响路由的 URL 路径名:</p>
<pre><code class="language-tree">src/routes/
+++│ (app)/+++
│ ├ dashboard/
│ ├ item/
│ └ +layout.svelte
+++│ (marketing)/+++
│ ├ about/
│ ├ testimonials/
│ └ +layout.svelte
├ admin/
└ +layout.svelte
</code></pre>
<p>您也可以直接将一个 <code>+page</code> 放在 <code>(group)</code> 中,例如如果 <code>/</code> 应该是一个 <code>(app)</code> 或 <code>(marketing)</code> 页面。</p>
<h3 id="跳出布局">跳出布局</h3>
<p>根布局适用于您的应用的每个页面 — 如果省略,它默认为 <code>{@render children()}</code>。如果您希望某些页面有不同于其余页面的布局层次结构,那么您可以将整个应用放在一个或多个组内,<em>除了</em> 不应继承公共布局的路由。</p>
<p>在上面的例子中,<code>/admin</code> 路由不继承 <code>(app)</code> 或 <code>(marketing)</code> 布局。</p>
<h3 id="page">+page@</h3>
<p>页面可以在逐个路由的基础上跳出当前布局层次结构。假设我们在前面例子的 <code>(app)</code> 组内有一个 <code>/item//embed</code> 路由:</p>
<pre><code class="language-tree">src/routes/
├ (app)/
│ ├ item/
│ │ ├ /
│ │ │ ├ embed/
+++│ │ │ │ └ +page.svelte+++
│ │ │ └ +layout.svelte
│ │ └ +layout.svelte
│ └ +layout.svelte
└ +layout.svelte
</code></pre>
<p>通常,这将继承根布局、<code>(app)</code> 布局、<code>item</code> 布局和 <code></code> 布局。我们可以通过附加 <code>@</code> 后跟路由段名来重置到其中一个布局 — 对于根布局,使用空字符串。在此例子中,我们可以选择以下选项:</p>
<ul>
<li><code>+page@.svelte</code> - 继承自 <code>src/routes/(app)/item//+layout.svelte</code></li>
<li><code>+page@item.svelte</code> - 继承自 <code>src/routes/(app)/item/+layout.svelte</code></li>
<li><code>+page@(app).svelte</code> - 继承自 <code>src/routes/(app)/+layout.svelte</code></li>
<li><code>+page@.svelte</code> - 继承自 <code>src/routes/+layout.svelte</code></li>
</ul>
<pre><code class="language-tree">src/routes/
├ (app)/
│ ├ item/
│ │ ├ /
│ │ │ ├ embed/
+++│ │ │ │ └ +page@(app).svelte+++
│ │ │ └ +layout.svelte
│ │ └ +layout.svelte
│ └ +layout.svelte
└ +layout.svelte
</code></pre>
<h3 id="layout">+layout@</h3>
<p>像页面一样,布局本身也可以使用相同的方式跳出其父布局层次结构。例如,<code>+layout@.svelte</code> 组件将将重置其所有子路由的层次结构。</p>
<pre><code>src/routes/
├ (app)/
│ ├ item/
│ │ ├ /
│ │ │ ├ embed/
│ │ │ │ └ +page.svelte// 使用 (app)/item//+layout.svelte
│ │ │ ├ +layout.svelte// 继承自 (app)/item/+layout@.svelte
│ │ │ └ +page.svelte // 使用 (app)/item/+layout@.svelte
│ │ └ +layout@.svelte // 继承自根布局,跳过 (app)/+layout.svelte
│ └ +layout.svelte
└ +layout.svelte
</code></pre>
<h3 id="何时使用布局分组">何时使用布局分组</h3>
<p>并非所有用例都适合使用布局分组,您也不应该觉得必须使用它们。可能您的用例会导致复杂的 <code>(group)</code> 嵌套,或者您不想为一个特例引入 <code>(group)</code>。使用其他方式如组合(可复用的 <code>load</code> 函数或 Svelte 组件)或 if 语句来实现您想要的效果也完全可以。以下示例展示了一个回退到根布局并复用其他布局也可以使用的组件和函数:</p>
<pre><code class="language-svelte"><!--- file: src/routes/nested/route/+layout@.svelte --->
<script>
import ReusableLayout from '$lib/ReusableLayout.svelte';
let { data, children } = $props();
</script>
<ReusableLayout {data}>
{@render children()}
</ReusableLayout>
</code></pre>
<pre><code class="language-js">/// 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 needed
return reusableLoad(event);
}
</code></pre>
<h2 id="进一步阅读">进一步阅读</h2>
<ul>
<li>教程:高级路由</li>
</ul>
<h2 id="svelte-中文文档">Svelte 中文文档</h2>
<p>点击查看中文文档:SvelteKit 高级路由</p>
<p>系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!</p>
<p>此外我还写过 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答读者问等 14 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog</p>
<p>欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。</p><br><br>
来源:https://www.cnblogs.com/yayujs/p/18796928
頁:
[1]