Skip to content

SvelteKit 最新中文文档教程(13)—— Hooks #364

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
mqyqingfeng opened this issue Mar 28, 2025 · 0 comments
Open

SvelteKit 最新中文文档教程(13)—— Hooks #364

mqyqingfeng opened this issue Mar 28, 2025 · 0 comments

Comments

@mqyqingfeng
Copy link
Owner

前言

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)
	}
};

进一步阅读

Svelte 中文文档

点击查看中文文档:SvelteKit 高级路由

系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

此外我还写过 JavaScript 系列TypeScript 系列React 系列Next.js 系列冴羽答读者问等 14 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant