## 剩余参数 如果路由段的数量未知,您可以使用剩余语法 - 例如,您可能可以这样实现 GitHub 的文件查看器... ```bash /[org]/[repo]/tree/[branch]/[...file] ``` ...在这种情况下,请求 `/sveltejs/kit/tree/main/documentation/docs/04-advanced-routing.md` 将使页面可用以下参数: ```js // @noErrors { org: 'sveltejs', repo: 'kit', branch: 'main', file: 'documentation/docs/04-advanced-routing.md' } ``` > \[!注意\] `src/routes/a/[...rest]/z/+page.svelte` 将匹配 `/a/z`(即没有任何参数)以及 `/a/b/z` 和 `/a/b/c/z` 等等。请确保检查其余参数值的有效性,例如使用一个 [匹配器](#Matching)。 ### 404 页面 余参数还允许您渲染自定义的 404 页面。给定这些路由... ```tree src/routes/ ├ marx-brothers/ │ ├ chico/ │ ├ harpo/ │ ├ groucho/ │ └ +error.svelte └ +error.svelte ``` ...文件 `marx-brothers/+error.svelte` 将不会被渲染,如果您访问 `/marx-brothers/karl`,因为没有匹配到路由。如果您想渲染嵌套的错误页面,您应该创建一个匹配任何 `/marx-brothers/*` 请求的路由,并从它返回一个 404: ```tree src/routes/ ├ marx-brothers/ +++| ├ [...path]/+++ │ ├ chico/ │ ├ harpo/ │ ├ groucho/ │ └ +error.svelte └ +error.svelte ``` ```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'); } ``` > \[注意\] 如果您不处理 404 情况,它们将在[`handleError`](hooks#Shared-hooks-handleError)中显示 ## 可选参数 一条类似于 `[lang]/home` 的路由包含一个名为 `lang` 的参数,该参数是必需的。有时将这些参数设置为可选是有益的,因此在这个例子中,`home` 和 `en/home` 都指向同一页面。您可以通过将参数包裹在另一对括号中来实现这一点:`[[lang]]/home` 请注意,可选路由参数不能紧接在 rest 参数之后(`[...rest]/[[optional]]`),因为参数是“贪婪”匹配的,可选参数将始终未被使用。 ## 匹配 一条类似于 `src/routes/fruits/[page]` 的路由会匹配 `/fruits/apple`,但也会匹配 `/fruits/rocketship`。我们不希望这样。您可以通过添加一个 *matcher* 来确保路由参数格式正确——该匹配器接受参数字符串(`"apple"` 或 `"rocketship"`)并返回 `true` 如果它是有效的——到您的 [`params`](configuration#files) 目录... ```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'; } ``` ...並增加您的路由: ``` src/routes/fruits/[page+++=fruit+++] ``` 如果路径名不匹配,SvelteKit 将尝试匹配其他路由(使用以下指定的排序顺序),最终返回 404。 每个`params`目录下的模块都对应一个匹配器,除了可能用于单元测试匹配器的`*.test.js`和`*.spec.js`文件。 > \[!注意\] 匹配器在服务器和浏览器上同时运行。 ## 排序 可能多个路由会匹配给定的路径。例如,以下每个路由都会匹配 `/foo-abc`: ```bash 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 需要知道正在请求哪个路由。为了做到这一点,它根据以下规则对它们进行排序... * 更具体的路由优先级更高(例如,没有参数的路由比有一个动态参数的路由更具体,依此类推) * 参数带有[匹配器](#Matching)(`[name=type]`)的优先级高于不带(`[name]`)的参数 * `[[可选]]` 和 `[...其余]` 参数除非是路由的最后一部分,否则将被忽略,在这种情况下,它们将被赋予最低优先级。换句话说,`x/[[y]]/z` 在排序方面等同于 `x/z`。 * 字母顺序解决冲突 ...导致这种排序,意味着`/foo-abc`将调用 `src/routes/foo-abc/+page.svelte` ,而`/foo-def`将调用 `src/routes/foo-[c]/+page.svelte` ,而不是更不具体的路由: ```bash 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 确定字符的十六进制代码: ```js ':'.charCodeAt(0).toString(16); // '3a', hence '[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 编程》](https://unicodebook.readthedocs.io/unicode_encodings.html)。 > \[!注意\] 由于 TypeScript [难以处理](https://github.com/microsoft/TypeScript/issues/13399) 以点号开头的目录,您在创建例如 [`.well-known`](https://en.wikipedia.org/wiki/Well-known_URI) 路由时可能发现对这些字符进行编码很有用: `src/routes/[x+2e]well-known/...` ## 高级布局 默认情况下,*布局层次结构*与*路由层次结构*相匹配。在某些情况下,这可能不是您想要的结果。 ### (組) 可能您有一些路由是 'app' 路由,应该使用一个布局(例如 `/dashboard` 或 `/item`),而其他的是 'marketing' 路由,应该使用不同的布局(`/about` 或 `/testimonials`)。我们可以将这些路由分组到一个以括号包裹的目录中——与普通目录不同,`(app)` 和 `(marketing)` 不会影响它们内部路由的 URL 路径名: ```tree src/routes/ +++│ (app)/+++ │ ├ dashboard/ │ ├ item/ │ └ +layout.svelte +++│ (marketing)/+++ │ ├ about/ │ ├ testimonials/ │ └ +layout.svelte ├ admin/ └ +layout.svelte ``` 您也可以直接在`(group)`中放置一个`+page`,例如,如果`/`应该是一个`(app)`或`(marketing)`页面。 ### 跳出布局 根布局适用于您应用的每个页面 — 如果省略,则默认为 `render children()`。如果您想某些页面具有与其他页面不同的布局层次结构,则可以将整个应用放入一个或多个组 *除外* 应该继承公共布局的路由。 在上述示例中,`/admin` 路由既不继承 `(app)` 也不继承 `(marketing)` 布局。 ### +页面@ 页面可以在路由级别逐个突破当前布局层次结构。假设我们有一个位于上一个示例中的 `(app)` 组内的 `/item/[id]/embed` 路由: ```tree 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` ```tree src/routes/ ├ (app)/ │ ├ item/ │ │ ├ [id]/ │ │ │ ├ embed/ +++│ │ │ │ └ +page@(app).svelte+++ │ │ │ └ +layout.svelte │ │ └ +layout.svelte │ └ +layout.svelte └ +layout.svelte ``` ### +布局@ 与页面类似,布局也可以*自身*跳出其父布局层次结构,使用相同的技巧。例如,一个`+layout@.svelte`组件将重置其所有子路由的层次结构。 ``` src/routes/ ├ (app)/ │ ├ item/ │ │ ├ [id]/ │ │ │ ├ embed/ │ │ │ │ └ +page.svelte // uses (app)/item/[id]/+layout.svelte │ │ │ ├ +layout.svelte // inherits from (app)/item/+layout@.svelte │ │ │ └ +page.svelte // uses (app)/item/+layout@.svelte │ │ └ +layout@.svelte // inherits from root layout, skipping (app)/+layout.svelte │ └ +layout.svelte └ +layout.svelte ``` ### 何时使用布局组 不是所有用例都适合布局分组,也不必强迫自己使用它们。可能你的用例会导致复杂的`(分组)`嵌套,或者你不想为单个异常引入一个`(分组)`。使用其他方法,如组合(可重用的`load`函数或 Svelte 组件)或 if 语句来实现你的目标是完全可行的。以下示例显示了一个布局,它回滚到根布局并重用其他布局也可以使用的组件和函数: ```svelte {@render children()} ``` ```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>; } // @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); } ``` ## 进一步阅读 * [ 教程:高级路由](/tutorial/kit/optional-params)