跳至主要內容

核心概念

載入資料

在 GitHub 上編輯此頁面

+page.svelte 元件(及其包含的 +layout.svelte 元件)可以被渲染之前,我們通常需要取得一些資料。這是透過定義 load 函式來完成的。

頁面資料

+page.svelte 檔案可以有一個兄弟檔 +page.js,它會匯出一個 load 函式,其回傳值會透過 data prop 提供給頁面

src/routes/blog/[slug]/+page.js
ts
/** @type {import('./$types').PageLoad} */
export function load({ params }) {
return {
post: {
title: `Title for ${params.slug} goes here`,
content: `Content for ${params.slug} goes here`
}
};
}
src/routes/blog/[slug]/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
return {
post: {
title: `Title for ${params.slug} goes here`,
content: `Content for ${params.slug} goes here`,
},
};
};
src/routes/blog/[slug]/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>

<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
src/routes/blog/[slug]/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>

感謝產生的 $types 模組,我們得到了完整的類型安全性。

+page.js 檔案中的 load 函式會同時在伺服器和瀏覽器上執行(除非與 export const ssr = false 結合,否則它將 僅在瀏覽器中執行)。如果你的 load 函式應該總是執行於伺服器上(例如,因為它使用私有環境變數或存取資料庫),那麼它將會轉而使用 +page.server.js

一個更實際的部落格文章 load 函式版本,它只會執行於伺服器上並從資料庫中提取資料,可能如下所示

src/routes/blog/[slug]/+page.server.js
ts
import * as db from '$lib/server/database';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
return {
post: await db.getPost(params.slug)
};
}
src/routes/blog/[slug]/+page.server.ts
ts
import * as db from '$lib/server/database';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
return {
post: await db.getPost(params.slug),
};
};

請注意,類型已從 PageLoad 變更為 PageServerLoad,因為伺服器 load 函式可以存取其他引數。若要了解何時使用 +page.js,何時使用 +page.server.js,請參閱 通用與伺服器

版面資料

您的 +layout.svelte 檔案也可以透過 +layout.js+layout.server.js 載入資料。

src/routes/blog/[slug]/+layout.server.js
ts
import * as db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function load() {
return {
posts: await db.getPostSummaries()
};
}
src/routes/blog/[slug]/+layout.server.ts
ts
import * as db from '$lib/server/database';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async () => {
return {
posts: await db.getPostSummaries(),
};
};
src/routes/blog/[slug]/+layout.svelte
<script>
	/** @type {import('./$types').LayoutData} */
	export let data;
</script>

<main>
	<!-- +page.svelte is rendered in this <slot> -->
	<slot />
</main>

<aside>
	<h2>More posts</h2>
	<ul>
		{#each data.posts as post}
			<li>
				<a href="/blog/{post.slug}">
					{post.title}
				</a>
			</li>
		{/each}
	</ul>
</aside>
src/routes/blog/[slug]/+layout.svelte
<script lang="ts">
	import type { LayoutData } from './$types';
	
	export let data: LayoutData;
</script>

<main>
	<!-- +page.svelte is rendered in this <slot> -->
	<slot />
</main>

<aside>
	<h2>More posts</h2>
	<ul>
		{#each data.posts as post}
			<li>
				<a href="/blog/{post.slug}">
					{post.title}
				</a>
			</li>
		{/each}
	</ul>
</aside>

從版面 load 函式傳回的資料可供子 +layout.svelte 元件和 +page.svelte 元件,以及它「所屬」的版面使用。

src/routes/blog/[slug]/+page.svelte
<script>
	import { page } from '$app/stores';

	/** @type {import('./$types').PageData} */
	export let data;

	// we can access `data.posts` because it's returned from
	// the parent layout `load` function
	$: index = data.posts.findIndex(post => post.slug === $page.params.slug);
	$: next = data.posts[index - 1];
</script>

<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>

{#if next}
	<p>Next post: <a href="/blog/{next.slug}">{next.title}</a></p>
{/if}

如果多個 load 函式傳回具有相同金鑰的資料,最後一個「獲勝」—版面 load 傳回 { a: 1, b: 2 },而頁面 load 傳回 { b: 3, c: 4 } 的結果將會是 { a: 1, b: 3, c: 4 }

$page.data

+page.svelte 元件,以及其上方的每個 +layout.svelte 元件,都可以存取其自己的資料,以及其父項目的所有資料。

在某些情況下,我們可能需要相反的情況—父版面可能需要存取頁面資料或子版面的資料。例如,根版面可能想要存取從 +page.js+page.server.js 中的 load 函式傳回的 title 屬性。這可以使用 $page.data 來完成

src/routes/+layout.svelte
<script>
	import { page } from '$app/stores';
</script>

<svelte:head>
	<title>{$page.data.title}</title>
</svelte:head>
src/routes/+layout.svelte
<script lang="ts">
	import { page } from '$app/stores';
</script>

<svelte:head>
	<title>{$page.data.title}</title>
</svelte:head>

$page.data 的類型資訊由 App.PageData 提供。

通用與伺服器

正如我們所見,有兩種 load 函式

  • +page.js+layout.js 檔案會匯出在伺服器和瀏覽器上執行的通用 load 函式
  • +page.server.js+layout.server.js 檔案會匯出僅在伺服器端執行的伺服器 load 函式

在概念上,它們是同一件事,但有一些重要的差異需要注意。

哪個載入函式在何時執行?

伺服器 load 函式總是在伺服器上執行。

預設情況下,通用 load 函式會在使用者第一次造訪您的頁面時,於 SSR 期間在伺服器上執行。然後,它們會在水化期間再次執行,重複使用來自 擷取請求 的任何回應。通用 load 函式的後續呼叫都會在瀏覽器中發生。您可以透過 頁面選項 自訂行為。如果您停用 伺服器端呈現,您將會取得 SPA,而通用 load 函式總是在客戶端執行。

如果路由同時包含通用和伺服器 load 函數,伺服器 load 會先執行。

load 函數會在執行階段呼叫,除非您 預先渲染 頁面,否則會在建置階段呼叫。

輸入

通用和伺服器 load 函數都可以存取描述請求的屬性(paramsrouteurl)和各種函數(fetchsetHeadersparentdependsuntrack)。這些屬性和函數會在以下各節中說明。

伺服器 load 函數會呼叫 ServerLoadEvent,它會從 RequestEvent 繼承 clientAddresscookieslocalsplatformrequest

通用 load 函數會呼叫 LoadEvent,它有一個 data 屬性。如果您在 +page.js+page.server.js(或 +layout.js+layout.server.js)中都有 load 函數,伺服器 load 函數的回傳值會是通用 load 函數參數的 data 屬性。

輸出

通用 load 函數可以回傳包含任何值的物件,包括自訂類別和元件建構函數。

伺服器 load 函數必須回傳可以用 devalue 序列化資料,也就是任何可以表示為 JSON 的資料,加上 BigIntDateMapSetRegExp,或重複/循環參照,以便透過網路傳輸。您的資料可以包含 承諾,如果包含,它會串流到瀏覽器。

何時使用哪一個

當您需要直接從資料庫或檔案系統存取資料,或需要使用私人環境變數時,伺服器 load 函數會很方便。

通用 load 函數在您需要從外部 API fetch 資料,且不需要私人憑證時很有用,因為 SvelteKit 可以直接從 API 取得資料,而不是透過您的伺服器。當您需要傳回無法序列化的內容時,例如 Svelte 元件建構函式,它們也很有用。

在少數情況下,您可能需要同時使用這兩個函數 — 例如,您可能需要傳回自訂類別的執行個體,而該執行個體已使用伺服器中的資料初始化。同時使用這兩個函數時,伺服器 load 傳回值不會直接傳遞給頁面,而是傳遞給通用 load 函數(作為 data 屬性)

src/routes/+page.server.js
ts
/** @type {import('./$types').PageServerLoad} */
export async function load() {
return {
serverMessage: 'hello from server load function'
};
}
src/routes/+page.server.ts
ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async () => {
return {
serverMessage: 'hello from server load function',
};
};
src/routes/+page.js
ts
/** @type {import('./$types').PageLoad} */
export async function load({ data }) {
return {
serverMessage: data.serverMessage,
universalMessage: 'hello from universal load function'
};
}
src/routes/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ data }) => {
return {
serverMessage: data.serverMessage,
universalMessage: 'hello from universal load function',
};
};

使用 URL 資料

通常,load 函數會以某種方式取決於 URL。為此,load 函數會提供 urlrouteparams 給您。

url

URL 的執行個體,包含 originhostnamepathnamesearchParams 等屬性(包含已剖析的查詢字串,為 URLSearchParams 物件)。url.hash 無法在 load 期間存取,因為它在伺服器上不可用。

在某些環境中,這會在伺服器端呈現期間從請求標頭中衍生而來。例如,如果您正在使用 adapter-node,您可能需要設定該轉接器,才能讓 URL 正確無誤。

route

包含目前路由目錄的名稱,相對於 src/routes

src/routes/a/[b]/[...c]/+page.js
ts
/** @type {import('./$types').PageLoad} */
export function load({ route }) {
console.log(route.id); // '/a/[b]/[...c]'
}
src/routes/a/[b]/[...c]/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = ({ route }) => {
console.log(route.id); // '/a/[b]/[...c]'
};

params

params 衍生自 url.pathnameroute.id

假設 route.id/a/[b]/[...c],而 url.pathname/a/x/y/z,則 params 物件會如下所示

ts
{
"b": "x",
"c": "y/z"
}

發出 fetch 請求

要從外部 API 或 +server.js 處理常式取得資料,可以使用提供的 fetch 函式,其行為與 原生 fetch 網路 API 相同,但具備一些額外功能

  • 它可用於在伺服器上進行憑證請求,因為它繼承了頁面請求的 cookieauthorization 標頭。
  • 它可以在伺服器上進行相對請求(通常,fetch 在伺服器環境中使用時需要具有來源的 URL)。
  • 內部請求(例如,對於 +server.js 路由)在伺服器上執行時會直接傳送至處理常式函式,而不會產生 HTTP 呼叫的負擔。
  • 在伺服器端渲染期間,回應會被擷取並透過連接到 Response 物件的 textjsonarrayBuffer 方法內嵌到已渲染的 HTML 中。請注意,標頭不會被序列化,除非透過 filterSerializedResponseHeaders 明確包含。
  • 在水化期間,回應會從 HTML 中讀取,保證一致性並防止額外的網路請求 - 如果你在使用瀏覽器 fetch 而不是 load fetch 時在瀏覽器主控台中收到警告,這就是原因。
src/routes/items/[id]/+page.js
ts
/** @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 };
}
src/routes/items/[id]/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch, params }) => {
const res = await fetch(`/api/items/${params.id}`);
const item = await res.json();
return { item };
};

Cookie

伺服器 load 函式可以取得和設定 cookie

src/routes/+layout.server.js
ts
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)
};
}
src/routes/+layout.server.ts
ts
import * as db from '$lib/server/database';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ cookies }) => {
const sessionid = cookies.get('sessionid');
return {
user: await db.getUser(sessionid),
};
};

Cookie 僅會透過提供的 fetch 函式傳遞,如果目標主機與 SvelteKit 應用程式相同,或為其更明確的子網域。

例如,如果 SvelteKit 服務 my.domain.com

  • domain.com 不會收到 cookie
  • my.domain.com 會收到 cookie
  • api.domain.com 不會收到 cookie
  • sub.my.domain.com 會收到 cookie

當設定 credentials: 'include' 時,其他 cookie 也不會被傳遞,因為 SvelteKit 不知道每個 cookie 屬於哪個網域(瀏覽器不會傳遞此資訊),所以轉送任何 cookie 都不安全。使用 handleFetch 鉤子 來解決這個問題。

標頭

伺服器和通用 load 函式都可以使用 setHeaders 函式,當在伺服器上執行時,可以設定回應的標頭。(在瀏覽器中執行時,setHeaders 沒有作用。)這在您希望頁面被快取時很有用,例如

src/routes/products/+page.js
ts
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, setHeaders }) {
const url = `https://cms.example.com/products.json`;
const response = await fetch(url);
// cache the page 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();
}
src/routes/products/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch, setHeaders }) => {
const url = `https://cms.example.com/products.json`;
const response = await fetch(url);
// cache the page 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 新增 set-cookie 標頭 - 請改用 cookies.set(name, value, options)

使用父層資料

偶爾,load 函式需要存取來自父層 load 函式的資料,這可以使用 await parent() 來完成

src/routes/+layout.js
ts
/** @type {import('./$types').LayoutLoad} */
export function load() {
return { a: 1 };
}
src/routes/+layout.ts
ts
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = () => {
return { a: 1 };
};
src/routes/abc/+layout.js
ts
/** @type {import('./$types').LayoutLoad} */
export async function load({ parent }) {
const { a } = await parent();
return { b: a + 1 };
}
src/routes/abc/+layout.ts
ts
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async ({ parent }) => {
const { a } = await parent();
return { b: a + 1 };
};
src/routes/abc/+page.js
ts
/** @type {import('./$types').PageLoad} */
export async function load({ parent }) {
const { a, b } = await parent();
return { c: a + b };
}
src/routes/abc/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ parent }) => {
const { a, b } = await parent();
return { c: a + b };
};
src/routes/abc/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>

<!-- renders `1 + 2 = 3` -->
<p>{data.a} + {data.b} = {data.c}</p>
src/routes/abc/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

<!-- renders `1 + 2 = 3` -->
<p>{data.a} + {data.b} = {data.c}</p>

請注意,+page.js 中的 load 函式會接收合併來自兩個版面 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() 的結果,所以我們應該先呼叫它以避免延遲渲染。

+page.js
/** @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 }
	};
}

錯誤

如果在 load 期間引發錯誤,最近的 +error.svelte 會被渲染。對於 預期的 錯誤,請使用 @sveltejs/kit 中的 error 輔助函式來指定 HTTP 狀態碼和一個可選的訊息

src/routes/admin/+layout.server.js
ts
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');
}
}
src/routes/admin/+layout.server.ts
ts
import { error } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = ({ locals }) => {
if (!locals.user) {
error(401, 'not logged in');
}
if (!locals.user.isAdmin) {
error(403, 'not an admin');
}
};

呼叫 `error(...)` 會擲回例外,讓您能輕鬆地從輔助函式內部停止執行。

如果擲回 意外 錯誤,SvelteKit 會呼叫 handleError,並將其視為 500 內部錯誤。

在 SvelteKit 1.x 中,您必須自己 `throw` 錯誤

重新導向

若要重新導向使用者,請使用 `@sveltejs/kit` 中的 `redirect` 輔助函式,以指定應將其重新導向到的位置,以及 `3xx` 狀態碼。與 `error(...)` 相同,呼叫 `redirect(...)` 會擲回例外,讓您能輕鬆地從輔助函式內部停止執行。

src/routes/user/+layout.server.js
ts
import { redirect } from '@sveltejs/kit';
/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
if (!locals.user) {
redirect(307, '/login');
}
}
src/routes/user/+layout.server.ts
ts
import { redirect } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = ({ locals }) => {
if (!locals.user) {
redirect(307, '/login');
}
};

請勿在 `try {...}` 區塊內使用 `redirect()`,因為重新導向會立即觸發 catch 陳述式。

在瀏覽器中,您也可以使用 goto$app.navigation 在 `load` 函式外部以程式方式導覽。

在 SvelteKit 1.x 中,您必須自己 `throw` `redirect`

使用 Promise 串流

使用伺服器 `load` 時,Promise 會在解析時串流到瀏覽器。如果您有非必要的慢速資料,這會很有用,因為您可以在所有資料都可用之前開始呈現頁面

src/routes/blog/[slug]/+page.server.js
ts
/** @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)
};
}
src/routes/blog/[slug]/+page.server.ts
ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ 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),
};
};

例如,這對於建立骨架載入狀態很有用

src/routes/blog/[slug]/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>

<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>

{#await data.comments}
	Loading comments...
{:then comments}
	{#each comments as comment}
		<p>{comment.content}</p>
	{/each}
{:catch error}
	<p>error loading comments: {error.message}</p>
{/await}
src/routes/blog/[slug]/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>

{#await data.comments}
	Loading comments...
{:then comments}
	{#each comments as comment}
		<p>{comment.content}</p>
	{/each}
{:catch error}
	<p>error loading comments: {error.message}</p>
{/await}

串流資料時,請小心正確處理 Promise 拒絕。更具體地說,如果延遲載入的 Promise 在開始呈現之前失敗(此時會被捕獲),而且沒有以某種方式處理錯誤,伺服器可能會因「未處理的 Promise 拒絕」錯誤而崩潰。在 `load` 函式中直接使用 SvelteKit 的 `fetch` 時,SvelteKit 會為您處理這個案例。對於其他 Promise,只要附加一個 noop-catch 到 Promise,將其標記為已處理就夠了。

src/routes/+page.server.js
ts
/** @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()
};
}
src/routes/+page.server.ts
ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = ({ 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)上,回應會被暫存。這表示頁面只會在所有 Promise 都解析後才會呈現。如果您使用代理伺服器(例如 NGINX),請確保它不會暫存來自代理伺服器的回應。

串流資料只會在啟用 JavaScript 時運作。如果您要伺服器呈現頁面,您應避免從通用 `load` 函式傳回 Promise,因為這些 Promise 不會 被串流 — 相反地,當函式在瀏覽器中重新執行時,Promise 會被重新建立。

一旦回應開始串流,就無法變更標頭和狀態碼,因此您無法在串流承諾中設定標頭或擲回重新導向。

在 SvelteKit 1.x 中,頂層承諾會自動等待,只有巢狀承諾會串流。

並行載入

在呈現(或導覽至)頁面時,SvelteKit 會同時執行所有 `load` 函式,避免請求瀑布效應。在用戶端導覽期間,呼叫多個伺服器 `load` 函式的結果會分組成單一回應。一旦所有 `load` 函式都傳回,頁面就會呈現。

重新執行載入函式

SvelteKit 會追蹤每個 `load` 函式的相依性,以避免在導覽期間不必要地重新執行它。

例如,給定一對像這樣的 `load` 函式...

src/routes/blog/[slug]/+page.server.js
ts
import * as db from '$lib/server/database';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
return {
post: await db.getPost(params.slug)
};
}
src/routes/blog/[slug]/+page.server.ts
ts
import * as db from '$lib/server/database';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
return {
post: await db.getPost(params.slug),
};
};
src/routes/blog/[slug]/+layout.server.js
ts
import * as db from '$lib/server/database';
/** @type {import('./$types').LayoutServerLoad} */
export async function load() {
return {
posts: await db.getPostSummaries()
};
}
src/routes/blog/[slug]/+layout.server.ts
ts
import * as db from '$lib/server/database';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async () => {
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` 函式也會重新執行。

相依性追蹤不適用於 `load` 函式傳回之後 — 例如,在巢狀 承諾 中存取 `params.x` 函式不會在 `params.x` 變更時導致函式重新執行。(別擔心,如果您不小心這樣做,您會在開發期間收到警告。)相反地,在 `load` 函式的 main body 中存取參數。

搜尋參數會獨立於 URL 的其他部分追蹤。例如,在 `load` 函式中存取 `event.url.searchParams.get("x")` 會在從 `?x=1` 導覽到 `?x=2` 時重新執行該 `load` 函式,但不會在從 `?x=1&y=1` 導覽到 `?x=1&y=2` 時重新執行。

取消追蹤相依性

在少數情況下,您可能希望從相依性追蹤機制中排除某些內容。您可以使用提供的 `untrack` 函式執行此操作

src/routes/+page.js
ts
/** @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!' };
}
}
src/routes/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ untrack, url }) => {
// Untrack url.pathname so that path changes don't trigger a rerun
if (untrack(() => url.pathname === '/')) {
return { message: 'Welcome!' };
}
};

手動失效

您也可以使用 invalidate(url) 重新執行適用於目前頁面的 `load` 函式,這會重新執行所有相依於 `url` 的 `load` 函式,以及 invalidateAll(),這會重新執行每個 `load` 函式。伺服器載入函式絕不會自動相依於已擷取的 `url`,以避免將機密洩漏給用戶端。

如果 `load` 函式呼叫 `fetch(url)` 或 `depends(url)`,則它相依於 `url`。請注意,`url` 可以是自訂識別碼,開頭為 `[a-z]:`:

src/routes/random-number/+page.js
ts
/** @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()
};
}
src/routes/random-number/+page.ts
ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ 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(),
};
};
src/routes/random-number/+page.svelte
<script>
	import { invalidate, invalidateAll } from '$app/navigation';

	/** @type {import('./$types').PageData} */
	export let data;

	function rerunLoadFunction() {
		// any of these will cause the `load` function to rerun
		invalidate('app:random');
		invalidate('https://api.example.com/random-number');
		invalidate(url => url.href.includes('random-number'));
		invalidateAll();
	}
</script>

<p>random number: {data.number}</p>
<button on:click={rerunLoadFunction}>Update random number</button>
src/routes/random-number/+page.svelte
<script lang="ts">
	import { invalidate, invalidateAll } from '$app/navigation';
	
	import type { PageData } from './$types';
	
	export let data: PageData;
	
	function rerunLoadFunction() {
		// any of these will cause the `load` function to rerun
		invalidate('app:random');
		invalidate('https://api.example.com/random-number');
		invalidate((url) => url.href.includes('random-number'));
		invalidateAll();
	}
</script>

<p>random number: {data.number}</p>
<button on:click={rerunLoadFunction}>Update random number</button>

何時會重新執行載入函式?

總之,load 函式會在以下情況重新執行

  • 它參照 params 的屬性,而該屬性的值已變更
  • 它參照 url 的屬性(例如 url.pathnameurl.search),而該屬性的值已變更。request.url 中的屬性不會被追蹤
  • 它呼叫 url.searchParams.get(...)url.searchParams.getAll(...)url.searchParams.has(...),而問題中的參數發生變更。存取 url.searchParams 的其他屬性會產生與存取 url.search 相同的效果。
  • 它呼叫 await parent(),而父 load 函式重新執行
  • 它透過 fetch(僅限通用載入)或 depends 宣告對特定 URL 的相依性,而該 URL 已使用 invalidate(url) 標記為無效
  • 所有 active load 函式都已使用 invalidateAll() 強制重新執行

paramsurl 可能會因應 <a href=".."> 連結點擊、<form> 互動、goto 呼叫或 redirect 而變更。

請注意,重新執行 load 函式會更新對應 +layout.svelte+page.svelte 內部的 data prop;它不會導致重新建立元件。因此,內部狀態會被保留。如果您不想要這樣的結果,您可以在 afterNavigate 回呼內部重設所有需要重設的內容,或者將元件包在 {#key ...} 區塊中。

對驗證的影響

載入資料的幾個功能對驗證檢查有重要的影響

  • Layout load 函式並非在每個請求中執行,例如在子路由之間的用戶端導覽期間。 (何時會重新執行載入函式?)
  • Layout 和 Page load 函式會同時執行,除非呼叫了 await parent()。如果 Layout load 擲回例外,Page load 函式會執行,但用戶端不會收到傳回的資料。

有幾種可能的策略可以確保驗證檢查在受保護程式碼之前發生。

為防止資料瀑布效應並保留版面配置,請快取 load

  • 在任何 load 函式執行之前,使用 hooks 來保護多個路由
  • +page.server.js load 函式中直接使用驗證防護,以保護特定路由

+layout.server.js 中放置驗證防護,則所有子頁面在執行受保護程式碼之前,都必須呼叫 await parent()。除非每個子頁面都依賴於 await parent() 傳回的資料,否則其他選項將會執行得更有效率。

進一步閱讀

上一個 路由
下一個 表單動作