在渲染一个 [`+page.svelte`](routing#page-page.svelte) 组件(及其包含的 [`+layout.svelte`](routing#layout-layout.svelte) 组件)之前,我们通常需要获取一些数据。这是通过定义 `load` 函数来完成的。
##
页面数据
一个 `+page.svelte` 文件可以有一个兄弟 `+page.js` 文件,该文件导出一个 `load` 函数,其返回值通过 `data` 属性在页面中可用:
```js
/// file: src/routes/blog/[slug]/+page.js
/** @type {import('./$types').PageLoad} */
export function load({ params }) {
return {
post: {
title: `Title for ${params.slug} goes here`,
content: `Content for ${params.slug} goes here`
}
};
}
```
```svelte
{data.post.title}
{@html data.post.content}
```
> \[!旧版\]在 2.16.0 版本之前,页面和布局的属性必须单独输入:
>
> ```js
> /// file: +page.svelte
> /** @type {{ data: import('./$types').PageData }} */
> let { data } = $props();
> ```
>
> 在 Svelte 4 中,您将使用 `export let data` 代替。
感谢生成的 `$types` 模块,我们获得了完全的类型安全。
一个在 `load` 文件中的 `+page.js` 函数在服务器和浏览器上都会运行(除非与 `export const ssr = false` 结合使用,在这种情况下它将 [仅在浏览器中运行](page-options#ssr))。如果你的 `load` 函数应该 *始终* 在服务器上运行(因为使用私有环境变量,例如,或访问数据库),那么它将放在 `+page.server.js` 中。
一个更现实的博客文章`load`函数版本,该函数仅在服务器上运行并从数据库中提取数据,可能看起来像这样:
```js
/// file: src/routes/blog/[slug]/+page.server.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function getPost(slug: string): Promise<{ title: string, content: string }>
}
// @filename: index.js
// ---cut---
import * as db from '$lib/server/database';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
return {
post: await db.getPost(params.slug)
};
}
```
注意:类型已从 `PageLoad` 更改为 `PageServerLoad`,因为服务器 `load` 函数可以访问额外的参数。要了解何时使用 `+page.js` 和何时使用 `+page.server.js`,请参阅 [通用与服务器](load#Universal-vs-server)。
##
布局数据
您的 `+layout.svelte` 文件也可以通过 `+layout.js` 或 `+layout.server.js` 加载数据。
```js
/// file: src/routes/blog/[slug]/+layout.server.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function getPostSummaries(): Promise>
}
// @filename: index.js
// ---cut---
import * as db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function load() {
return {
posts: await db.getPostSummaries()
};
}
```
```svelte
{@render children()}
```
> \[!旧版\] `LayoutProps` 在 2.16.0 版本中添加。在早期版本中,属性必须单独指定类型:
>
> ```js
> /// file: +layout.svelte
> /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */
> let { data, children } = $props();
> ```
数据从 `load` 函数返回,可供子 `+layout.svelte` 组件以及 `+page.svelte` 组件以及它所属的布局使用。
```svelte
/// file: src/routes/blog/[slug]/+page.svelte
{data.post.title}
{@html data.post.content}
+++{#if next}
Next post: {next.title}
{/if}+++
```
> \[注意\] 如果多个 `load` 函数返回具有相同键的数据,则最后一个“获胜”——一个布局 `load` 返回 `{ a: 1, b: 2 }` 和一个页面 `load` 返回 `{ b: 3, c: 4 }` 的结果将是 `{ a: 1, b: 3, c: 4 }`。
##
页面数据
组件 `+page.svelte` 以及其上方的每个 `+layout.svelte` 组件,都可以访问自己的数据以及其父级的所有数据。
在某些情况下,我们可能需要相反的情况——父布局可能需要访问页面数据或子布局的数据。例如,根布局可能希望访问从`+page.js`或`+page.server.js`中的`load`函数返回的`title`属性。这可以通过`page.data`来实现:
```svelte
{page.data.title}
```
类型信息由 `page.data` 提供,由 `App.PageData` 提供。
> \[!旧版\] `$app/state` 在 SvelteKit 2.12 中被添加。如果您使用的是更早的版本或使用 Svelte 4,请使用 `$app/stores` 代替。它提供了一个具有相同接口的 `page` 存储,您可以订阅它,例如 `$page.data.title`。
##
通用与服务器
我们已看到,有两种类型的 `load` 函数:
* `+page.js` 和 `+layout.js` 文件导出 *通用*`加载` 函数,这些函数在服务器和浏览器上都能运行
* `+page.server.js` 和 `+layout.server.js` 文件导出 *server*`load` 函数,这些函数仅在服务器端运行
概念上,它们是同一件事,但有一些重要的差异需要注意。
###
何时运行哪个加载函数?
服务器 `加载` 函数 *总是* 在服务器上运行。
默认情况下,通用 `加载` 函数在 SSR(服务器端渲染)期间用户首次访问您的页面时在服务器上运行。然后它们将在 hydration 期间再次运行,重用来自 [fetch 请求](#Making-fetch-requests) 的任何响应。所有后续的通用 `加载` 函数调用都发生在浏览器中。您可以通过 [页面选项](page-options) 定制行为。如果您禁用 [服务器端渲染](page-options#ssr),您将获得一个 SPA,并且通用 `加载` 函数 *始终* 在客户端运行。
如果路由同时包含通用和服务器 `加载` 函数,则服务器 `加载` 先运行。
A `加载`函数在运行时被调用,除非您[预渲染](page-options#prerender)页面——在这种情况下,它在构建时被调用。
###
输入
两者都通用的和服务器 `load` 函数可以访问描述请求的属性(`params`、`route` 和 `url`)以及各种函数(`fetch`、`setHeaders`、`parent`、`depends` 和 `untrack`)。这些内容在以下章节中描述。
服务器 `load` 函数通过一个 `ServerLoadEvent` 被调用,该事件继承自 `clientAddress`、`cookies`、`locals`、`platform` 和 `request`,它们都来自 `RequestEvent`。
通用`load`函数使用`LoadEvent`调用,该事件具有`data`属性。如果您在`+page.js`和`+page.server.js`(或`+layout.js`和`+layout.server.js`)中都有`load`函数,则服务器`load`函数的返回值是通用`load`函数参数的`data`属性。
###
Input: Output:
一个通用的`load`函数可以返回一个包含任何值的对象,包括像自定义类和组件构造函数这样的东西。
服务器 `加载` 函数必须返回可以与 [反序列化](https://github.com/rich-harris/devalue) 一并序列化的数据——任何可以表示为 JSON 的内容以及类似 `BigInt`、`Date`、`Map`、`Set` 和 `RegExp` 的内容,或者重复/循环引用——以便通过网络传输。如果您的数据包含 [承诺](#Streaming-with-promises),则将流式传输到浏览器。如果您需要序列化/反序列化自定义类型,请使用 [传输钩子](https://svelte.dev/docs/kit/hooks#Universal-hooks-transport)。
###
何时使用哪个
服务器 `加载` 函数在您需要直接从数据库或文件系统访问数据或需要使用私有环境变量时很方便。
通用 `加载` 函数在您需要从外部 API `获取` 数据且不需要私有凭证时很有用,因为 SvelteKit 可以直接从 API 获取数据,而不是通过您的服务器。当您需要返回无法序列化的内容时,例如 Svelte 组件构造函数,它们也非常有用。
在罕见情况下,您可能需要同时使用两者——例如,您可能需要返回一个使用从您的服务器加载数据初始化的自定义类的实例。当同时使用时,服务器的 `load` 返回值不是直接传递给页面,而是传递给通用的 `load` 函数(作为 `data` 属性):
```js
/// file: src/routes/+page.server.js
/** @type {import('./$types').PageServerLoad} */
export async function load() {
return {
serverMessage: 'hello from server load function'
};
}
```
```js
/// file: src/routes/+page.js
// @errors: 18047
/** @type {import('./$types').PageLoad} */
export async function load({ data }) {
return {
serverMessage: data.serverMessage,
universalMessage: 'hello from universal load function'
};
}
```
##
使用 URL 数据
通常,`load` 函数以某种方式依赖于 URL。为此,`load` 函数为您提供了 `url`、`route` 和 `params`。
### url
一个包含如`origin`、`hostname`、`pathname`和`searchParams`(其中包含解析后的查询字符串作为[`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)对象)等属性的[`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL)实例。在`load`期间无法访问`url.hash`,因为在服务器上不可用。
> \[注意\] 在某些环境中,这是在服务器端渲染期间从请求头中派生出来的。如果您使用[adapter-node](adapter-node),例如,您可能需要配置适配器,以便 URL 正确。
###
路由
包含当前路由目录名称,相对于 `src/routes`:
```js
/// file: src/routes/a/[b]/[...c]/+page.js
/** @type {import('./$types').PageLoad} */
export function load({ route }) {
console.log(route.id); // '/a/[b]/[...c]'
}
```
### params
`params` 由 `url.pathname` 和 `route.id` 派生。
给定一个 `route.id` 的 `/a/[b]/[...c]` 和一个 `url.pathname` 的 `/a/x/y/z`,`params` 对象将如下所示:
`{ "b": "x" c: y/z }`
##
制作获取请求
要从外部 API 或一个`+server.js`处理器获取数据,您可以使用提供的`fetch`函数,该函数的行为与[原生的`fetch`网络 API](https://developer.mozilla.org/en-US/docs/Web/API/fetch)相同,并增加了几个额外功能:
* 它可以用来在服务器上发起认证请求,因为它继承了页面请求的`cookie`和`authorization`头部。
* 它可以对服务器进行相对请求(通常情况下,在服务器环境中使用 `fetch` 需要一个带有源头的 URL)。
* 内部请求(例如对 `+server.js` 路由的请求)在服务器上运行时直接发送到处理函数,无需 HTTP 调用的开销。
* 在服务器端渲染过程中,响应将被捕获并内联到渲染的 HTML 中,通过挂钩到`text`、`json`和`arrayBuffer`方法以及`Response`对象。请注意,除非明确通过 [`filterSerializedResponseHeaders`](hooks#Server-hooks-handle) 包含,否则头部信息将不会被序列化。
* 在加水过程中,响应将从 HTML 中读取,确保一致性并防止额外的网络请求 - 如果您在使用浏览器时在控制台收到警告,使用的是`fetch`而不是`load``fetch`,这就是原因。
```js
/// file: src/routes/items/[id]/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, params }) {
const res = await fetch(`/api/items/${params.id}`);
const item = await res.json();
return { item };
}
```
## Cookies
服务器 `加载` 函数可以获取和设置 [`cookies`](@sveltejs-kit#Cookies)。
```js
/// file: src/routes/+layout.server.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function getUser(sessionid: string | undefined): Promise<{ name: string, avatar: string }>
}
// @filename: index.js
// ---cut---
import * as db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function load({ cookies }) {
const sessionid = cookies.get('sessionid');
return {
user: await db.getUser(sessionid)
};
}
```
Cookies 仅当目标主机与 SvelteKit 应用程序或其更具体的子域名相同时,才会通过提供的 `fetch` 函数传递。
例如,如果 SvelteKit 正在为 my.domain.com 提供服务:
* domain.com 不会接收 cookies
* my.domain.com 将会接收 cookies
* api.domain.com 不会接收 cookies
* sub.my.domain.com 将会接收 cookies
其他 cookie 在设置`credentials: 'include'`时不会被传递,因为 SvelteKit 不知道哪个 cookie 属于哪个域名(浏览器不会传递这些信息),因此转发任何这些 cookie 都不安全。使用[handleFetch 钩子](hooks#Server-hooks-handleFetch)来解决这个问题。
##
头信息
服务器和通用 `load` 函数都可以访问一个 `setHeaders` 函数,当在服务器上运行时,可以设置响应的头部信息。(在浏览器中运行时,`setHeaders` 无效。)如果您想使页面被缓存,这很有用,例如:
```js
// @errors: 2322 1360
/// file: src/routes/products/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, setHeaders }) {
const url = `https://cms.example.com/products.json`;
const response = await fetch(url);
// Headers are only set during SSR, caching the page's HTML
// for the same length of time as the underlying data.
setHeaders({
age: response.headers.get('age'),
'cache-control': response.headers.get('cache-control')
});
return response.json();
}
```
设置相同的标题多次(即使在单独的 `load` 函数中)是错误的。您只能使用 `setHeaders` 函数设置给定的标题一次。您不能使用 `setHeaders` 添加 `set-cookie` 标题 — 请使用 `cookies.set(name, value, options)` 代替。
##
使用父数据
偶尔,对于 `加载` 函数来说,访问父 `加载` 函数中的数据是有用的,这可以通过 `await parent()` 实现:
```js
/// file: src/routes/+layout.js
/** @type {import('./$types').LayoutLoad} */
export function load() {
return { a: 1 };
}
```
```js
/// file: src/routes/abc/+layout.js
/** @type {import('./$types').LayoutLoad} */
export async function load({ parent }) {
const { a } = await parent();
return { b: a + 1 };
}
```
```js
/// file: src/routes/abc/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ parent }) {
const { a, b } = await parent();
return { c: a + b };
}
```
```svelte
{data.a} + {data.b} = {data.c}
```
> \[!注意\] 注意到 `load` 函数在 `+page.js` 中接收来自布局 `load` 函数的合并数据,而不仅仅是直接父级。
在`+page.server.js`和`+layout.server.js`内部,`parent`从父`+layout.server.js`文件中返回数据。
在`+page.js`或`+layout.js`中,它将返回父`+layout.js`文件中的数据。然而,缺失的`+layout.js`被视为一个`({ data }) => data`函数,这意味着它也将返回父`+layout.server.js`文件中的数据,这些数据不会被`+layout.js`文件“覆盖”。
请注意在使用`await parent()`时不要引入瀑布效应。例如,这里`getData(params)`不依赖于调用`parent()`的结果,因此我们应该先调用它,以避免延迟渲染。
```js
/// file: +page.js
// @filename: ambient.d.ts
declare function getData(params: Record): Promise<{ meta: any }>
// @filename: index.js
// ---cut---
/** @type {import('./$types').PageLoad} */
export async function load({ params, parent }) {
---const parentData = await parent();---
const data = await getData(params);
+++const parentData = await parent();+++
return {
...data,
meta: { ...parentData.meta, ...data.meta }
};
}
```
##
错误
如果在 `加载` 过程中抛出错误,则将渲染最近的 [`+error.svelte`](routing#error)。对于 [*预期*](errors#Expected-errors) 错误,请使用来自 `@sveltejs/kit` 的 `error` 辅助函数来指定 HTTP 状态码和可选消息:
```js
/// file: src/routes/admin/+layout.server.js
// @filename: ambient.d.ts
declare namespace App {
interface Locals {
user?: {
name: string;
isAdmin: boolean;
}
}
}
// @filename: index.js
// ---cut---
import { error } from '@sveltejs/kit';
/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
if (!locals.user) {
error(401, 'not logged in');
}
if (!locals.user.isAdmin) {
error(403, 'not an admin');
}
}
```
调用 `error(...)` 将会抛出异常,这使得在辅助函数内部停止执行变得容易。
如果抛出[*意外*](errors#Unexpected-errors)错误,SvelteKit 将调用[`handleError`](hooks#Shared-hooks-handleError)并将其视为 500 内部错误。
> \[!注意\] [在 SvelteKit 1.x 中](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you)您必须 `手动抛出` 错误
##
重定向
要重定向用户,请使用来自 `@sveltejs/kit` 的 `redirect` 辅助函数来指定应重定向到的位置,并附带一个 `3xx` 状态码。类似于调用 `error(...)`,调用 `redirect(...)` 将会抛出异常,这使得在辅助函数内部停止执行变得容易。
```js
/// file: src/routes/user/+layout.server.js
// @filename: ambient.d.ts
declare namespace App {
interface Locals {
user?: {
name: string;
}
}
}
// @filename: index.js
// ---cut---
import { redirect } from '@sveltejs/kit';
/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
if (!locals.user) {
redirect(307, '/login');
}
}
```
> \[注意\] 不要在 `try {...}` 块内使用 `redirect()`,因为重定向将立即触发 catch 语句。
在浏览器中,您还可以使用 [`$app.navigation`]($app-navigation) 中的 [`goto`]($app-navigation#goto) 从 `load` 函数外进行程序化导航。
> \[!注意\] [在 SvelteKit 1.x 中](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you)您必须自己 `抛出` `重定向`
##
流式传输与承诺
当使用服务器 `加载` 时,一旦解决,承诺将被流式传输到浏览器。如果您有缓慢的非关键数据,这很有用,因为您可以在所有数据可用之前开始渲染页面:
```js
/// file: src/routes/blog/[slug]/+page.server.js
// @filename: ambient.d.ts
declare global {
const loadPost: (slug: string) => Promise<{ title: string, content: string }>;
const loadComments: (slug: string) => Promise<{ content: string }>;
}
export {};
// @filename: index.js
// ---cut---
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
return {
// make sure the `await` happens at the end, otherwise we
// can't start loading comments until we've loaded the post
comments: loadComments(params.slug),
post: await loadPost(params.slug)
};
}
```
这对于创建骨架加载状态很有用,例如:
```svelte
{data.post.title}
{@html data.post.content}
{#await data.comments}
Loading comments...
{:then comments}
{#each comments as comment}
{comment.content}
{/each}
{:catch error}
error loading comments: {error.message}
{/await}
```
在流式传输数据时,请小心正确处理承诺拒绝。更具体地说,如果延迟加载的承诺在渲染开始之前失败(此时会被捕获)并且没有以某种方式处理错误,服务器可能会因为“未处理的承诺拒绝”错误而崩溃。当在`load`函数中直接使用 SvelteKit 的`fetch`时,SvelteKit 会为你处理这种情况。对于其他承诺,只需将一个 noop-`catch`附加到承诺上,即可将其标记为已处理。
```js
/// file: src/routes/+page.server.js
/** @type {import('./$types').PageServerLoad} */
export function load({ fetch }) {
const ok_manual = Promise.reject();
ok_manual.catch(() => {});
return {
ok_manual,
ok_fetch: fetch('/fetch/that/could/fail'),
dangerous_unhandled: Promise.reject()
};
}
```
> \[注意\] 在不支持流式传输的平台,例如 AWS Lambda 或 Firebase 上,响应将被缓冲。这意味着页面将仅在所有承诺解决后才会渲染。如果您正在使用代理(例如 NGINX),请确保它不会缓冲代理服务器的响应。
> \[注意\] 仅当启用 JavaScript 时,流式数据才会工作。如果页面是服务器渲染的,应避免从通用的`load`函数返回承诺,因为这些不是*流式*的——相反,当函数在浏览器中重新运行时,承诺将被重新创建。
> \[!注意\] 响应一旦开始流式传输,其头部和状态码就不能更改,因此您不能在流式承诺中 `设置头部` 或抛出重定向。
> \[!注意\] [在 SvelteKit 1.x 中](migrating-to-sveltekit-2#Top-level-promises-are-no-longer-awaited)顶级承诺自动等待,只有嵌套承诺被流式传输。
##
并行加载
当渲染(或导航到)页面时,SvelteKit 会并发运行所有`load`函数,避免请求瀑布效应。在客户端导航期间,调用多个服务器`load`函数的结果被组合成一个单一响应。一旦所有`load`函数返回,页面就会被渲染。
##
重新运行加载函数
SvelteKit 跟踪每个 `load` 函数的依赖关系,以避免在导航过程中不必要地重新运行它。
例如,给定一对 `load` 函数,如下所示...
```js
/// file: src/routes/blog/[slug]/+page.server.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function getPost(slug: string): Promise<{ title: string, content: string }>
}
// @filename: index.js
// ---cut---
import * as db from '$lib/server/database';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
return {
post: await db.getPost(params.slug)
};
}
```
```js
/// file: src/routes/blog/[slug]/+layout.server.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function getPostSummaries(): Promise>
}
// @filename: index.js
// ---cut---
import * as db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function load() {
return {
posts: await db.getPostSummaries()
};
}
```
...在`+page.server.js`中的代码将在我们从 `/blog/trying-the-raw-meat-diet` 导航到`/blog/i-regret-my-choices`时重新运行,因为`params.slug`已更改。在`+layout.server.js`中的代码不会,因为数据仍然有效。换句话说,我们不会再次调用`db.getPostSummaries()`。
一个调用 `load` 函数的 `await parent()` 也会在父 `load` 函数重新运行时重新运行。
依赖跟踪在 *加载* 函数返回后不适用 —— 例如,在嵌套的 [承诺](#Streaming-with-promises) 中访问 `params.x` 不会导致函数重新运行,当 `params.x` 发生变化时。 (别担心,如果你不小心这样做,开发过程中你会收到警告。) 相反,请在你的 `load` 函数的主体中访问该参数。
搜索参数独立于 URL 的其余部分进行跟踪。例如,在`load`函数内部访问 `event.url.searchParams.get("x")` 将使该`load`函数在从`?x=1`导航到`?x=2`时重新运行,但不会在从`?x=1&y=1`导航到`?x=1&y=2`时重新运行。
###
取消跟踪依赖
在罕见情况下,您可能希望从依赖跟踪机制中排除某些内容。您可以使用提供的 `untrack` 函数来完成此操作:
```js
/// file: src/routes/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ untrack, url }) {
// Untrack url.pathname so that path changes don't trigger a rerun
if (untrack(() => url.pathname === '/')) {
return { message: 'Welcome!' };
}
}
```
###
手动失效
您也可以使用`load`函数重新运行适用于当前页面的功能,通过[`invalidate(url)`]($app-navigation#invalidate),这将重新运行所有依赖于`url`的`load`函数,以及[`invalidateAll()`]($app-navigation#invalidateAll),这将重新运行每个`load`函数。服务器负载函数永远不会自动依赖于获取的`url`,以避免将秘密泄露给客户端。
一个 `load` 函数在调用 `url` 时依赖于它,如果它调用了 `fetch(url)` 或 `depends(url)`。请注意,`url` 可以是一个以 `[a-z]:` 开头的自定义标识符。
```js
/// file: src/routes/random-number/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, depends }) {
// load reruns when `invalidate('https://api.example.com/random-number')` is called...
const response = await fetch('https://api.example.com/random-number');
// ...or when `invalidate('app:random')` is called
depends('app:random');
return {
number: await response.json()
};
}
```
```svelte
random number: {data.number}
```
###
何时重新运行加载函数?
总结来说,以下情况下将重新运行 `load` 函数:
* 它引用了`params`属性的一个值已更改的属性
* 它引用了一个属性 `url`(例如 `url.pathname` 或 `url.search`),其值已更改。在 `request.url` 中的属性 *未*被跟踪
* 它调用 `url.searchParams.get(...)`,`url.searchParams.getAll(...)` 或 `url.searchParams.has(...)` 并更改相关参数。访问 `url.searchParams` 的其他属性将产生与访问 `url.search` 相同的效果。
* 它调用 `await parent()` 和一个父 `load` 函数重新运行
* 一个子 `load` 函数调用 `await parent()` 并正在重新运行,而父函数是一个服务器负载函数
* 它通过[`fetch`](#Making-fetch-requests)(仅限通用加载)或[`depends`](@sveltejs-kit#LoadEvent)声明了对特定 URL 的依赖,并且该 URL 被[`invalidate(url)`]($app-navigation#invalidate)标记为无效
* 所有活动 `load` 函数均被强制重新运行,使用 [`invalidateAll()`]($app-navigation#invalidateAll)
`params` 和 `url` 可以根据 `` 链接点击、[`