高级路由
剩余参数
如果路由段的数量未知,您可以使用剩余语法 — 例如您可以像这样实现 GitHub 的文件查看器...
/[org]/[repo]/tree/[branch]/[...file]
...在这种情况下,对 /sveltejs/kit/tree/main/documentation/docs/04-advanced-routing.md
的请求将导致以下参数可供页面使用:
{
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
等。请确保检查剩余参数的值是否有效,例如使用匹配器。
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
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
/** @type {import('./$types').PageLoad} */
export function function load(event: any): void
load(event: any
event) {
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(404, 'Not Found');
}
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>
event) => {
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(404, 'Not Found');
};
如果您不处理 404 情况,它们将出现在
handleError
中
可选参数
像 [lang]/home
这样的路由包含一个名为 lang
的必需参数。有时需要让这些参数成为可选的,这样在这个例子中 home
和 en/home
都指向同一个页面。您可以通过将参数包裹在另一对括号中来实现:[[lang]]/home
注意,可选路由参数不能跟在剩余参数后面([...rest]/[[optional]]
),因为参数是”贪婪”匹配的,可选参数永远不会被使用。
匹配
像 src/routes/fruits/[page]
这样的路由会匹配 /fruits/apple
,但它也会匹配 /fruits/rocketship
。我们不希望这样。您可以通过在 params
目录中添加一个 匹配器 — 它接收参数字符串("apple"
或 "rocketship"
)并在有效时返回 true
— 来确保路由参数格式正确...
/**
* @param {string} param
* @return {param is ('apple' | 'orange')}
* @satisfies {import('@sveltejs/kit').ParamMatcher}
*/
export function function match(param: any): boolean
match(param: any
param) {
return param: any
param === 'apple' || param: any
param === 'orange';
}
import type { type ParamMatcher = (param: string) => boolean
The shape of a param matcher. See matching for more info.
ParamMatcher } from '@sveltejs/kit';
export const const match: (param: string) => param is ("apple" | "orange")
match = ((param: string
param: string): param: string
param is ('apple' | 'orange') => {
return param: string
param === 'apple' || param: string
param === 'orange';
}) satisfies type ParamMatcher = (param: string) => boolean
The shape of a param matcher. See matching for more info.
ParamMatcher;
...并增强您的路由:
src/routes/fruits/[page=fruit]
如果路径名不匹配,SvelteKit 将尝试匹配其他路由(使用下面指定的排序顺序),最终返回 404。
params
目录中的每个模块对应一个匹配器,除了 *.test.js
和 *.spec.js
文件,这些文件可用于对匹配器进行单元测试。
匹配器在服务端和浏览器中都会运行。
排序
多个路由可能匹配同一个路径。例如,以下每个路由都会匹配 /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 确定字符的十六进制代码:
':'.String.charCodeAt(index: number): number
Returns the Unicode value of the character at the specified location.
charCodeAt(0).Number.toString(radix?: number): string
Returns a string representation of an object.
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 编程。
由于 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 语句来实现您想要的效果也完全可以。以下示例展示了一个回退到根布局并复用其他布局也可以使用的组件和函数:
<script>
import ReusableLayout from '$lib/ReusableLayout.svelte';
let { data, children } = $props();
</script>
<ReusableLayout {data}>
{@render children()}
</ReusableLayout>
<script lang="ts">
import ReusableLayout from '$lib/ReusableLayout.svelte';
let { data, children } = $props();
</script>
<ReusableLayout {data}>
{@render children()}
</ReusableLayout>
import { function reusableLoad(event: import("@sveltejs/kit").LoadEvent): Promise<Record<string, any>>
reusableLoad } from '$lib/reusable-load-function';
/** @type {import('./$types').PageLoad} */
export function function load(event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load(event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>
event) {
// Add additional logic here, if needed
return function reusableLoad(event: import("@sveltejs/kit").LoadEvent): Promise<Record<string, any>>
reusableLoad(event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>
event);
}
import { function reusableLoad(event: import("@sveltejs/kit").LoadEvent): Promise<Record<string, any>>
reusableLoad } from '$lib/reusable-load-function';
import type { type PageLoad = (event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = (event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>
event) => {
// Add additional logic here, if needed
return function reusableLoad(event: import("@sveltejs/kit").LoadEvent): Promise<Record<string, any>>
reusableLoad(event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>
event);
};