Skip to main content

sveltekit-data

SvelteKit 数据加载、表单、页面选项技能。当用户在 SvelteKit 中使用 +page.js/+page.server.js 加载数据、使用 +server.js API 路由、处理表单 actions(默认/具名/GET)、使用 use:enhance 进行渐进增强、配置 prerender/ssr/csr/trailingSlash 页面选项时使用。

الانتقال إلى التثبيت

معلومات المصدر

المستودع
full-stack-skills/svelte-skills
آخر نشاط في المصدر
١١ سبتمبر ٢٠٢٦ في ١٣:٤٣
لغة SKILL.md المكتشفة
الصينية
النجوم
٣
التفرعات
٢

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
10 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
sveltekit-data
license
Apache-2.0
description
SvelteKit 数据加载、表单、页面选项技能。当用户在 SvelteKit 中使用 +page.js/+page.server.js 加载数据、使用 +server.js API 路由、处理表单 actions(默认/具名/GET)、使用 use:enhance 进行渐进增强、配置 prerender/ssr/csr/trailingSlash 页面选项时使用。
# SvelteKit Data: Loading, Form Actions, Page Options (SvelteKit 2) 本技能覆盖 SvelteKit 数据层的三大支柱:(1) Loading data(`+page.js` / `+page.server.js` / `+layout.js` / `+layout.server.js` 的 `load` 函数、`page.data`、URL 数据、cookies/headers、错误与重定向、流式响应、依赖追踪与手动失效),(2) Form actions(默认/具名 actions、`fail` 验证、`use:enhance` 渐进增强、`GET` vs `POST`),(3) Page options(`prerender` / `entries` / `ssr` / `csr` / `trailingSlash` / `config`)。 ## When to use this skill - 决定 `+page.js` vs `+page.server.js` 的取舍(隐私凭据、序列化、组合使用) - 在 `load` 函数中使用 `params` / `route` / `url` / `fetch` / `parent` / `depends` / `untrack` - 用 `page.data` 在父布局访问子页面数据 - 用 `error(status, msg)` / `redirect(status, location)` 终止 load/action - 用返回 Promise 实现 streaming 骨架屏 - 解决"load 函数何时重新执行"以及如何用 `invalidate` / `invalidateAll` / `untrack` 精确控制 - 在 server `load` / action 中读 `cookies` / 设响应头 - 设计 `+page.server.js` 的 actions:默认 action、具名 action、`?/name` 查询参数、`fail()` 验证 - 用 `use:enhance` 渐进增强表单、回填 `form` 字段、阻止默认重置、自定义 `applyAction` - 决定哪些路由 `prerender = true` / `entries()` / `ssr = false` / `csr = false` / `trailingSlash` ## Critical: Loading data (universal vs server load functions) SvelteKit 有两种 `load` 函数,运行位置和约束不同。 **Universal load**(`+page.js` / `+layout.js`):默认 SSR 时在服务端跑一次、客户端 hydration 时再跑一次;之后所有导航都在浏览器内运行。可以返回**任意 JS 值**(包括 Svelte 组件构造函数等不可序列化的对象)。`fetch` 在 SSR 阶段内联到 HTML,hydration 时复用,不会泄漏私密凭据到客户端。 **Server load**(`+page.server.js` / `+layout.server.js`):**永远在服务端跑**。返回值必须用 [devalue](https://github.com/rich-harris/devalue) 序列化(JSON + `BigInt` / `Date` / `Map` / `Set` / `RegExp` / 循环引用)。可访问 `cookies` / `locals` / `request` / `clientAddress` / `platform`。 ```js // +page.server.js import * as db from '$lib/server/database'; /** @type {import('./$types').PageServerLoad} */ export async function load({ params }) { return { post: await db.getPost(params.slug) }; } ``` ```js // +page.js —— 公共 API fetch,浏览器可直接拉 /** @type {import('./$types').PageLoad} */ export async function load({ fetch, params }) { const res = await fetch(`/api/items/${params.id}`); return { item: await res.json() }; } ``` **两者可以同时存在**。当同时存在时,server load 先跑,其返回值成为 universal load 的 `data` 参数;universal load 的返回值才到达页面: ```js // +page.server.js —— 仅在服务端,返回 sessionId export async function load({ locals }) { return { sessionId: locals.sessionId }; } // +page.js —— 浏览器也能跑,但能拿到服务端上下文 export async function load({ data, fetch }) { const res = await fetch(`/api/me`, { headers: { 'x-session': data.sessionId } }); return { me: await res.json() }; } ``` **何时用哪个**: - 需要私密环境变量、数据库、文件系统 → server load - 数据是公开 API、且希望减少服务器往返 → universal load - 需要返回不可序列化对象(Svelte 组件 class) → universal load - 需要 streaming(慢数据 + 骨架屏) → **必须 server load**(universal load 的 Promise 不会被流式传输) - 需要在客户端也能重新跑 → universal load ## Critical: page.data (sharing data across components) 页面和所有祖先 layout 各有**自己的** `data` prop,包含它自己 + 全部祖先的合并数据。当**父**组件需要**子**组件返回的数据时,用 `$app/state` 的 `page.data`(`$app/stores` 的 `$page` 是旧式等价物): ```svelte <!-- src/routes/+layout.svelte --> <script> import { page } from '$app/state'; /** @type {import('./$types').LayoutProps} */ let { data, children } = $props(); </script> <svelte:head> <title>{page.data.title}</title> </svelte:head> {@render children()} ``` 合并规则:同级出现相同 key 时**后者覆盖**。`+layout.js` 返回 `{ a:1, b:2 }` + `+page.js` 返回 `{ b:3, c:4 }` → `data = { a:1, b:3, c:4 }`。 ## Critical: URL data (params/route/url) `load` 函数通过 `url` / `route` / `params` 访问 URL。`url.hash` 在 SSR 阶段不可用(服务端无 fragment)。 ```js // src/routes/a/[b]/[...c]/+page.js /** @type {import('./$types').PageLoad} */ export function load({ url, route, params }) { // route.id = '/a/[b]/[...c]' // url = URL 实例(origin/pathname/searchParams/...) // params.b = 'x' params.c = 'y/z' return { q: url.searchParams.get('q') }; } ``` `url.searchParams.get/getAll/has` 在依赖追踪中是**独立**的 key——`?x=1&y=1` → `?x=1&y=2` **不会**重跑只依赖 `y` 的 load。 ## Critical: Cookies and Headers **只有 server load** 可以读 / 写 `cookies`。`setHeaders` 在 universal load 中调用 SSR 时生效,浏览器内调用是 no-op。 ```js // +layout.server.js export async function load({ cookies }) { const sessionid = cookies.get('sessionid'); return { user: await db.getUser(sessionid) }; } // +page.js —— 转发上游 cache 头 export async function load({ fetch, setHeaders }) { const response = await fetch('https://cms.example.com/products.json'); setHeaders({ 'cache-control': response.headers.get('cache-control') }); return response.json(); } ``` 约束:`setHeaders` 同名 header 只能设一次;不能用 `setHeaders` 设 `set-cookie`(用 `cookies.set`);`fetch` 只在**同源或子域**目标带 cookies,其他域用 `handleFetch` hook。 ## Critical: Errors and Redirects `error(status, message)` 和 `redirect(status, location)` 都**直接抛异常**——不要自己 `throw`(SvelteKit 1.x 行为已废弃)。`redirect` **不要放在 try 块里**(会被 catch 吃掉)。 ```js import { error, redirect } from '@sveltejs/kit'; export function load({ locals }) { if (!locals.user) error(401, 'not logged in'); if (!locals.user.isAdmin) error(403, 'not an admin'); } export function load({ locals, url }) { if (!locals.user) { const next = url.pathname + url.search; redirect(303, `/login?redirectTo=${encodeURIComponent(next)}`); } } ``` `expected error`(用 `error()` 抛出)显示最近 `+error.svelte` 并带正确 status;`unexpected error` 触发 `handleError` hook,按 500 处理。 浏览器端导航用 `$app/navigation` 的 `goto`: ```js import { goto } from '$app/navigation'; goto('/login'); ``` ## Critical: Streaming with promises Server `load` 返回**未 await 的 Promise** 会被流式传输到浏览器,允许快数据先渲染、慢数据后到。 ```js // +page.server.js —— 关键数据先 await,慢数据后流 export async function load({ params }) { return { post: await loadPost(params.slug), comments: loadComments(params.slug) // 不 await → 单独流 }; } ``` ```svelte <!-- +page.svelte --> <h1>{data.post.title}</h1> {#await data.comments} <p>Loading comments...</p> {:then comments} {#each comments as c}<p>{c.content}</p>{/each} {:catch error} <p>error: {error.message}</p> {/await} ``` **强约束**: - streaming 仅在 JS 启用且非 Lambda/Firebase 等缓冲平台时生效 - 响应开始流式输出后**不能**再 `setHeaders` / `redirect` - 手写 Promise 加 `.catch(() => {})` 防止 unhandled rejection;`fetch` 由 SvelteKit 自动处理 - 嵌套 promise 内的 `params.x` 不会被依赖追踪——必须在顶层 body 访问 ## Critical: When load functions rerun SvelteKit 跟踪每个 load 的依赖以避免重跑。`load` 重新执行的条件: 1. 访问 `params` 某属性且值变了 2. 访问 `url.pathname` / `url.search` 等且值变了 3. `url.searchParams.get/getAll/has` 对应参数变了 4. `await parent()` 且父 load 重跑了 5. 通过 `fetch(url)` 或 `depends(url)` 声明依赖,且 `invalidate(url)` 被调用 6. `invalidateAll()` 强制重跑所有 active load ```js // +page.js —— 自定义依赖标签(约定 [a-z]: 前缀) export async function load({ fetch, depends }) { depends('app:random'); const r = await fetch('https://api.example.com/random-number'); return { number: await r.json() }; } ``` ```svelte <script> import { invalidate, invalidateAll } from '$app/navigation'; function rerun() { invalidate('app:random'); invalidate('https://api.example.com/random-number'); invalidate(url => url.href.includes('random-number')); invalidateAll(); } </script> <button onclick={rerun}>refresh</button> ``` **server load 不会**自动依赖 fetch 的 URL(避免泄漏凭据)——必须 `depends(url)` 显式声明。用 `untrack(fn)` 排除依赖: ```js export async function load({ untrack, url }) { if (untrack(() => url.pathname === '/')) return { message: 'Welcome!' }; } ``` **重跑 ≠ 重建组件**。`+layout.svelte` / `+page.svelte` 实例保留,只有 `data` prop 更新,组件内部 state 保留。需要强制重建 → 用 `{#key page.url.pathname}`。 ## Critical: Form actions (default/named, validation, redirects) `+page.server.js` 导出 `actions` 对象提供 `<form>` 端点。**action 总是 POST**(GET 不应有副作用)。 **Default action**: ```js // src/routes/login/+page.server.js /** @satisfies {import('./$types').Actions} */ export const actions = { default: async (event) => { /* ... */ } }; ``` ```svelte <form method="POST"> <input name="email"> <input name="password" type="password"> <button>Log in</button> </form> ``` **Named actions**:用 `?/name` 区分: ```js export const actions = { login: async (event) => { /* ... */ }, register: async (event) => { /* ... */ } }; ``` ```svelte <form method="POST" action="?/login">...</form> <form method="POST" action="/login?/register">...</form> <!-- 跨页调用 --> <!-- 同表单不同按钮 --> <form method="POST" action="?/login"> <button>Login</button> <button formaction="?/register">Register</button> </form> ``` **重要**:default + named 不能共存——若 POST 具名 action 不 redirect,`?/name` 留在 URL 里,下次 default POST 也会命中它。 **Validation errors**:`fail(status, data)` 返回 status + 数据,data 进 `form` prop / `page.form` / `page.status`: ```js import { fail } from '@sveltejs/kit'; export const actions = { login: async ({ cookies, request }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password'); if (!email) return fail(400, { email, missing: true }); const user = await db.getUser(email); if (!user || user.password !== db.hash(password)) { return fail(400, { email, incorrect: true }); } cookies.set('sessionid', await db.createSession(user), { path: '/' }); return { success: true }; } };
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub