动态

详情 返回 返回

SvelteKit 最新中文文檔教程(13)—— Hooks - 动态 详情

前言

Svelte,一個語法簡潔、入門容易,面向未來的前端框架。

從 Svelte 誕生之初,就備受開發者的喜愛,根據統計,從 2019 年到 2024 年,連續 6 年一直是開發者最感興趣的前端框架 No.1

image.png

Svelte 以其獨特的編譯時優化機制著稱,具有輕量級高性能易上手等特性,非常適合構建輕量級 Web 項目

為了幫助大家學習 Svelte,我同時搭建了 Svelte 最新的中文文檔站點。

如果需要進階學習,也可以入手我的小冊《Svelte 開發指南》,語法篇、實戰篇、原理篇三大篇章帶你係統掌握 Svelte!

歡迎圍觀我的“網頁版朋友圈”、加入“冴羽·成長陪伴社羣”,踏上“前端大佬成長之路”。

Hooks

“Hooks” 是您聲明的應用程序範圍的函數,SvelteKit 會在響應特定事件時調用它們,讓您能夠對框架的行為進行更為精細的控制。

有三個 hook 文件,都是可選的:

  • src/hooks.server.js — 您的應用程序的服務端 hook
  • src/hooks.client.js — 您的應用程序的客户端 hook
  • src/hooks.js — 您的應用程序的在客户端和服務端都運行的 hook

這些模塊中的代碼會在應用程序啓動時運行,這使得它們對初始化數據庫客户端等操作很有用。

[!NOTE] 您可以通過 config.kit.files.hooks 配置這些文件的位置。

服務端 hook

以下 hook 可以添加到 src/hooks.server.js 中:

handle

這個函數在 SvelteKit 服務端每次接收到 request 時運行 — 無論是在應用程序運行時,還是在預渲染過程中 — 並決定response。

它接收一個表示請求的 event 對象和一個名為 resolve 的函數,該函數渲染路由並生成一個 Response。這允許您修改響應頭或響應體,或完全繞過 SvelteKit(例如,用於以編程方式實現路由)。

/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
    if (event.url.pathname.startsWith('/custom')) {
        return new Response('custom response');
    }

    const response = await resolve(event);
    return response;
}
[!NOTE] 對靜態資源的請求 — 包括已經預渲染的頁面 — 不會由 SvelteKit 處理。

如果未實現,默認為 ({ event, resolve }) => resolve(event)

locals

要向請求中添加自定義數據(這些數據會傳遞給 +server.js 中的處理程序和服務端的 load 函數),可以填充 event.locals 對象,如下所示。

/// file: src/hooks.server.js
// @filename: ambient.d.ts
type User = {
    name: string;
}

declare namespace App {
    interface Locals {
        user: User;
    }
}

const getUserInformation: (cookie: string | void) => Promise<User>;

// @filename: index.js
// ---cut---
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
    event.locals.user = await getUserInformation(event.cookies.get('sessionid'));

    const response = await resolve(event);
    response.headers.set('x-custom-header', 'potato');

    return response;
}

您可以定義多個 handle 函數,並使用sequence 輔助函數執行它們。

resolve 還支持第二個可選參數,讓您能夠更好地控制響應的渲染方式。該參數是一個對象,可以包含以下字段:

  • transformPageChunk(opts: { html: string, done: boolean }): MaybePromise<string | undefined> — 對 HTML 應用自定義轉換。如果 done 為 true,則是最後一個塊。塊不保證是格式良好的 HTML(例如,它們可能包含一個元素的開始標籤但沒有結束標籤),但它們總是會在合理的邊界處分割,比如 %sveltekit.head% 或佈局/頁面組件。
  • filterSerializedResponseHeaders(name: string, value: string): boolean — 確定當 load 函數使用 fetch 加載資源時,哪些頭部應該包含在序列化的響應中。默認情況下,不會包含任何頭部。
  • preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean — 確定應該在 <head> 標籤中添加哪些文件以預加載。該方法在構建代碼塊時被調用,每個找到的文件都會被調用 — 例如,如果您在 +page.svelte 中有 import './styles.css,在訪問該頁面時,preload 將傳入該 CSS 文件的解析路徑進行調用。注意,在開發模式下不會調用 preload,因為它依賴於構建時的分析。預加載可以通過更早下載資源來提高性能,但如果不必要地下載太多內容也會適得其反。默認情況下,會預加載 jscss 文件。目前不會預加載 asset 文件,但我們可能會在評估反饋後添加此功能。
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
    const response = await resolve(event, {
        transformPageChunk: ({ html }) => html.replace('old', 'new'),
        filterSerializedResponseHeaders: (name) => name.startsWith('x-'),
        preload: ({ type, path }) => type === 'js' || path.includes('/important/')
    });

    return response;
}

注意,resolve(...) 永遠不會拋出錯誤,它總是會返回一個帶有適當狀態碼的 Promise<Response>。如果在 handle 期間其他地方拋出錯誤,這將被視為致命錯誤,SvelteKit 將根據 Accept 頭部返回錯誤的 JSON 表示或回退錯誤頁面 — 後者可以通過 src/error.html 自定義。您可以在這裏閲讀更多關於錯誤處理的信息。

handleFetch

這個函數允許您修改(或替換)在服務端上運行的 loadaction 函數中發生的 fetch 請求(或在預渲染期間)。

例如,當用户執行客户端導航到相應頁面時,您的 load 函數可能會向公共 URL(如 https://api.yourapp.com)發出請求,但在 SSR 期間,直接訪問 API 可能更有意義(繞過位於它和公共互聯網之間的代理和負載均衡器)。

/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ request, fetch }) {
    if (request.url.startsWith('https://api.yourapp.com/')) {
        // 克隆原始請求,但改變 URL
        request = new Request(
            request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
            request
        );
    }

    return fetch(request);
}

認證憑據

對於同源請求,除非 credentials 選項設置為 "omit",否則 SvelteKit 的 fetch 實現會轉發 cookieauthorization 頭部。

對於跨源請求,如果請求 URL 屬於應用程序的子域,則會包含 cookie — 例如,如果您的應用程序在 my-domain.com 上,而您的 API 在 api.my-domain.com 上,cookie 將包含在請求中。

如果您的應用程序和 API 在兄弟子域上 — 例如 www.my-domain.comapi.my-domain.com — 那麼屬於共同父域(如 my-domain.com)的 cookie 將不會被包含,因為 SvelteKit 無法知道 cookie 屬於哪個域。在這些情況下,您需要使用 handleFetch 手動包含 cookie:

/// file: src/hooks.server.js
// @errors: 2345
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ event, request, fetch }) {
    if (request.url.startsWith('https://api.my-domain.com/')) {
        request.headers.set('cookie', event.request.headers.get('cookie'));
    }

    return fetch(request);
}

共享 hook

以下 hook 可以同時添加到 src/hooks.server.jssrc/hooks.client.js 中:

handleError

如果在加載或渲染期間拋出意外錯誤,此函數將被調用,並傳入 erroreventstatus 代碼和 message。這允許兩件事:

  • 您可以記錄錯誤
  • 您可以生成一個安全的、顯示給用户的自定義錯誤表示,省略敏感的詳細信息,如消息和堆棧跟蹤。返回的值(默認為 { message })會成為 $page.error 的值。

對於從您的代碼(或您的代碼調用的庫代碼)拋出的錯誤,狀態將為 500,消息將為 "Internal Error"。雖然 error.message 可能包含不應暴露給用户的敏感信息,但 message 是安全的(儘管對普通用户來説沒有意義)。

要以類型安全的方式向 $page.error 對象添加更多信息,您可以通過聲明 App.Error 接口(必須包含 message: string,以保證合理的回退行為)來自定義預期的形狀。這允許您 — 例如 — 附加一個跟蹤 ID,供用户在與技術支持人員通信時引用:

/// file: src/app.d.ts
declare global {
    namespace App {
        interface Error {
            message: string;
            errorId: string;
        }
    }
}

export {};
/// file: src/hooks.server.js
// @errors: 2322 2353
// @filename: ambient.d.ts
declare module '@sentry/sveltekit' {
    export const init: (opts: any) => void;
    export const captureException: (error: any, opts: any) => void;
}

// @filename: index.js
// ---cut---
import * as Sentry from '@sentry/sveltekit';

Sentry.init({/*...*/})

/** @type {import('@sveltejs/kit').HandleServerError} */
export async function handleError({ error, event, status, message }) {
    const errorId = crypto.randomUUID();

    // 與 https://sentry.io/ 集成的示例
    Sentry.captureException(error, {
        extra: { event, errorId, status }
    });

    return {
        message: '哎呀!',
        errorId
    };
}
/// file: src/hooks.client.js
// @errors: 2322 2353
// @filename: ambient.d.ts
declare module '@sentry/sveltekit' {
    export const init: (opts: any) => void;
    export const captureException: (error: any, opts: any) => void;
}

// @filename: index.js
// ---cut---
import * as Sentry from '@sentry/sveltekit';

Sentry.init({/*...*/})

/** @type {import('@sveltejs/kit').HandleClientError} */
export async function handleError({ error, event, status, message }) {
    const errorId = crypto.randomUUID();

    // 與 https://sentry.io/ 集成的示例
    Sentry.captureException(error, {
        extra: { event, errorId, status }
    });

    return {
        message: '哎呀!',
        errorId
    };
}
[!NOTE] 在 src/hooks.client.js 中,handleError 的類型是 HandleClientError 而不是 HandleServerError,並且 event 是一個 NavigationEvent 而不是 RequestEvent

此函數不會因為預期的錯誤(那些使用從 @sveltejs/kit 導入的 error 函數拋出的錯誤)而被調用。

在開發過程中,如果由於 Svelte 代碼中的語法錯誤而發生錯誤,傳入的錯誤會附加一個 frame 屬性,突出顯示錯誤的位置。

[!NOTE] 確保 handleError 永遠不會拋出錯誤

init

這個函數在服務端創建或應用程序在瀏覽器中啓動時運行一次,是執行異步工作(如初始化數據庫連接)的有用位置。

[!NOTE] 如果您的環境支持頂級 await,init 函數實際上與在模塊頂層編寫初始化邏輯沒有什麼不同,但一些環境 — 尤其是 Safari — 不支持。
/// file: src/hooks.server.js
import * as db from '$lib/server/database';

/** @type {import('@sveltejs/kit').ServerInit} */
export async function init() {
    await db.connect();
}
[!NOTE]
在瀏覽器中,init 中的異步工作會延遲水合,所以要注意您在那裏放什麼。

通用 hook

以下 hook 可以添加到 src/hooks.js 中。通用 hook 在服務端和客户端都運行(不要與共享 hook 混淆,後者是特定環境的)。

reroute

這個函數在 handle 之前運行,允許您更改 URL 如何轉換為路由。返回的路徑名(默認為 url.pathname)用於選擇路由及其參數。

例如,您可能有一個 src/routes/[[lang]]/about/+page.svelte 頁面,它應該可以訪問為 /en/about/de/ueber-uns/fr/a-propos。您可以用 reroute 來實現:

/// file: src/hooks.js
// @errors: 2345
// @errors: 2304

/** @type {Record<string, string>} */
const translated = {
    '/en/about': '/en/about',
    '/de/ueber-uns': '/de/about',
    '/fr/a-propos': '/fr/about'
};

/** @type {import('@sveltejs/kit').Reroute} */
export function reroute({ url }) {
    if (url.pathname in translated) {
        return translated[url.pathname];
    }
}

lang 參數將從返回的路徑名正確派生。

使用 reroute 不會改變瀏覽器地址欄的內容,也不會改變 event.url 的值。

傳輸

這是一組 傳輸器,允許您跨服務端/客户端邊界傳遞自定義類型 - 從 load 和 form actions 返回的類型。每個傳輸器都包含一個 encode 函數,該函數對服務端上的值進行編碼(或對任何不是該類型的實例返回 false),以及一個相應的 decode 函數:

/// file: src/hooks.js
import { Vector } from '$lib/math';

/** @type {import('@sveltejs/kit').Transport} */
export const transport = {
    Vector: {
        encode: (value) => value instanceof Vector && [value.x, value.y],
        decode: ([x, y]) => new Vector(x, y)
    }
};

進一步閲讀

  • 教程: Hooks

Svelte 中文文檔

點擊查看中文文檔:SvelteKit 高級路由

系統學習 Svelte,歡迎入手小冊《Svelte 開發指南》。語法篇、實戰篇、原理篇三大篇章帶你係統掌握 Svelte!

此外我還寫過 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答讀者問等 14 個系列文章, 全系列文章目錄:https://github.com/mqyqingfeng/Blog

歡迎圍觀我的“網頁版朋友圈”、加入“冴羽·成長陪伴社羣”,踏上“前端大佬成長之路”。

Add a new 评论

Some HTML is okay.