前言
Svelte,一个语法简洁、入门容易,面向未来的前端框架。
从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1:
Svelte 以其独特的编译时优化机制著称,具有轻量级、高性能、易上手等特性,非常适合构建轻量级 Web 项目。
为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。
如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!
欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。
数据加载
在渲染一个 +page.svelte 组件(及其包含的 +layout.svelte 组件)之前,我们通常需要获取一些数据。这是通过定义 load 函数来实现的。
页面数据
一个 +page.svelte 文件可以有一个同级的 +page.js 文件,该文件导出一个 load 函数,该函数的返回值可以通过 data 属性在页面中使用:- /// 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`
- }
- };
- }
复制代码- <h1>{data.post.title}</h1>
- {@html data.post.content}
复制代码[!LEGACY]
在 Svelte 4 中,您需要使用 export let data 代替
得益于生成的 $types 模块,我们获得了完整的类型安全性。
+page.js 文件中的 load 函数在服务端和浏览器上都会运行(除非与 export const ssr = false 结合使用,在这种情况下它将仅在浏览器中运行)。如果您的 load 函数应该始终在服务端上运行(例如,因为它使用了私有环境变量或访问数据库),那么它应该放在 +page.server.js 中。
一个更贴合实际的博客文章 load 函数示例,它只在服务端上运行并从数据库中获取数据。可能如下所示:- /// 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文档:高级路由 请参阅 Universal 与 server。
布局数据
您的 +layout.svelte 文件也可以通过 +layout.js 或 +layout.server.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()
- };
- }
复制代码- <main>
-
- {@render children()}
- </main>
- <h2>More posts</h2>
- <ul>
- {#each data.posts as post}
- <li>
-
- {post.title}
-
- </li>
- {/each}
- </ul>
- </aside>
复制代码 布局 load 函数返回的数据对子 +layout.svelte 组件和 +page.svelte 组件以及它"所属"的布局都可用。- /// file: src/routes/blog/[slug]/+page.svelte<h1>{data.post.title}</h1>
- {@html data.post.content}+++{#if next} Next post: {next.title}
- {/if}+++
复制代码[!NOTE] 如果多个 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 实现:- <svelte:head>
- <title>{page.data.title}</title>
- </svelte:head>
复制代码 page.data 的类型信息由 App.PageData 提供。
[!LEGACY] > $app/state 是在 SvelteKit 2.12 中添加的。如果您使用的是早期版本或使用 Svelte 4,请使用 $app/stores 代替。它提供了一个具有相同接口的 page store,您可以订阅它,例如 $page.data.title。
Universal vs server
正如我们所见,有两种类型的 load 函数:
- +page.js 和 +layout.js 文件导出的在服务端和浏览器上都运行的通用 load 函数
- +page.server.js 和 +layout.server.js 文件导出的只在服务端运行的服务端 load 函数
从概念上讲,它们是相同的东西,但有一些重要的区别需要注意。
何时运行哪个 load 函数?
服务端 load 函数总是在服务端上运行。
默认情况下,通用 load 函数在用户首次访问页面时在 SSR 期间在服务端上运行。然后它们会在水合过程中再次运行,复用来自 fetch 请求的任何响应。所有后续调用通用 load 函数都发生在浏览器中。您可以通过页面选项自定义该行为。如果您禁用了服务端渲染,您将获得一个 SPA,通用 load 函数始终在客户端运行。
如果一个路由同时包含通用和服务端 load 函数,服务端 load 函数会先运行。
除非您预渲染页面 - 在这种情况下,它会在构建时被调用,否则 load 函数会在运行时被调用。
输入
通用和服务端 load 函数都可以访问描述请求的属性(params、route 和 url)以及各种函数(fetch、setHeaders、parent、depends 和 untrack)。这些在后面的章节中会描述。
服务端 load 函数使用 ServerLoadEvent 调用,它从 RequestEvent 继承 clientAddress、cookies、locals、platform 和 request。
通用 load 函数使用具有 data 属性的 LoadEvent 调用。如果您在 +page.js 和 +page.server.js(或 +layout.js 和 +layout.server.js)中都有 load 函数,则服务端 load 函数的返回值是通用 load 函数参数的 data 属性。
输出
通用 load 函数可以返回包含任何值的对象,包括自定义类和组件构造函数等内容。
服务端 load 函数必须返回可以用 devalue 序列化的数据 - 任何可以用 JSON 表示的内容,以及像 BigInt、Date、Map、Set 和 RegExp 这样的内容,或重复/循环引用 - 这样它才能通过网络传输。您的数据可以包含promises,在这种情况下它将被流式传输到浏览器。
何时使用哪个
当您需要直接访问数据库或文件系统,或需要使用私有环境变量时,服务端 load 函数很方便。
当您需要从外部 API fetch 数据且不需要私有凭据时,通用 load 函数很有用,因为 SvelteKit 可以直接从 API 获取数据而无需通过服务端。当您需要返回无法序列化的内容(如 Svelte 组件构造函数)时,它们也很有用。
在极少数情况下,您可能需要同时使用两者 - 例如,您可能需要返回一个使用服务端数据初始化的自定义类的实例。当同时使用两者时,服务端 load 的返回值不会直接传递给页面,而是传递给通用 load 函数(作为 data 属性):- /// file: src/routes/+page.server.js
- /** @type {import('./$types').PageServerLoad} */
- export async function load() {
- return {
- serverMessage: 'hello from server load function'
- };
- }
复制代码- /// 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
URL 的一个实例,包含诸如 origin、hostname、pathname 和 searchParams(包含解析后的查询字符串,作为 URLSearchParams 对象)等属性。在 load 期间无法访问 url.hash,因为它在服务端上不可用。
[!NOTE] 在某些环境中,这是在服务端渲染期间从请求头派生的。例如,如果您使用 adapter-node,您可能需要配置适配器以使 URL 正确。
route
包含当前路由目录相对于 src/routes 的名称:- /// 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//[...c] 且 url.pathname 为 /a/x/y/z 时,params 对象将如下所示:发起 fetch 请求
要从外部 API 或 +server.js 处理程序获取数据,您可以使用提供的 fetch 函数,它的行为与原生 fetch web API完全相同,但有一些额外的功能:
- 它可以在服务端上发起带凭据的请求,因为它继承了页面请求的 cookie 和 authorization 标头。
- 它可以在服务端上发起相对请求(通常,当在服务端上下文中使用时,fetch 需要带有源的 URL)。
- 内部请求(例如对 +server.js 路由的请求)在服务端上运行时直接转到处理函数,无需 HTTP 调用的开销。
- 在服务端渲染期间,通过钩入 text、json 和 arrayBuffer 方法来捕获响应并将其内联到渲染的 HTML 中 Response 对象。请注意,除非通过 filterSerializedResponseHeaders 显式包含,否则标头将不会被序列化。
- 在水合过程中,响应将从 HTML 中读取,确保一致性并防止额外的网络请求 - 如果在使用浏览器 fetch 而不是 loadfetch 时,在浏览器控制台中收到警告,这就是原因。
- /// 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
服务端 load 函数可以获取和设置cookies。- /// 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)
- };
- }
复制代码 只有当目标主机与 SvelteKit 应用程序相同或是其更具体的子域名时,Cookie 才会通过提供的 fetch 函数传递。
例如,如果 SvelteKit 正在为 my.domain.com 提供服务:
- domain.com 将不会接收 cookies
- my.domain.com 将会接收 cookies
- api.domain.com 将不会接收 cookies
- sub.my.domain.com 将会接收 cookies
当设置 credentials: 'include' 时,其他 cookies 将不会被传递,因为 SvelteKit 无法知道哪个 cookie 属于哪个域(浏览器不会传递这些信息),所以转发任何 cookie 都是不安全的。使用 handleFetch hook 钩子来解决这个问题。
Headers
服务端和通用 load 函数都可以访问 setHeaders 函数,当在服务端上运行时,可以为响应设置头部信息。(在浏览器中运行时,setHeaders 不会产生效果。)这在你想要缓存页面时很有用,例如:- // @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) 代替。
使用父级数据
有时候让 load 函数访问父级 load 函数中的数据是很有用的,这可以通过 await parent() 实现:- /// file: src/routes/+layout.js
- /** @type {import('./$types').LayoutLoad} */
- export function load() {
- return { a: 1 };
- }
复制代码- /// file: src/routes/abc/+layout.js
- /** @type {import('./$types').LayoutLoad} */
- export async function load({ parent }) {
- const { a } = await parent();
- return { b: a + 1 };
- }
复制代码- /// file: src/routes/abc/+page.js
- /** @type {import('./$types').PageLoad} */
- export async function load({ parent }) {
- const { a, b } = await parent();
- return { c: a + b };
- }
复制代码- <p>{data.a} + {data.b} = {data.c}</p>
复制代码[!NOTE] 注意,+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.js 文件"遮蔽"的父级 +layout.server.js 文件中的数据。
使用 await parent() 时要注意避免瀑布流。例如,getData(params) 并不依赖于调用 parent() 的结果,所以我们应该先调用它以避免延迟渲染。- /// file: +page.js
- // @filename: ambient.d.ts
- declare function getData(params: Record<string, string>): 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 }
- };
- }
复制代码 Errors
如果在 load 期间抛出错误,将渲染最近的 +error.svelte。对于预期的错误,使用来自 @sveltejs/kit 的 error 辅助函数来指定 HTTP 状态码和可选消息:- /// 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(...) 将抛出一个异常,这使得在辅助函数内部停止执行变得容易。
如果抛出了一个意外错误,SvelteKit 将调用 handleError 并将其视为 500 内部错误。
[!NOTE] 在 SvelteKit 1.x 中,你必须自己 throw 错误
Redirects
要重定向用户,请使用来自 @sveltejs/kit 的 redirect 辅助函数,以指定用户应被重定向到的位置以及一个 3xx 状态码。与 error(...) 类似,调用 redirect(...) 将抛出一个异常,这使得在辅助函数内部停止执行变得容易。- /// 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');
- }
- }
复制代码[!NOTE] 不要在 try {...} 块内使用 redirect(),因为重定向会立即触发 catch 语句。
在浏览器中,你也可以在 load 函数之外使用来自 $app.navigation 的 goto 通过编程的方式进行导航。
[!NOTE] 在 SvelteKit 1.x 中,你必须自己 throw 这个 redirect
Streaming with promises
当使用服务端 load 时,Promise 将在 resolve 时流式传输到浏览器。如果你有较慢的、非必要的数据,这很有用,因为你可以在所有数据可用之前开始渲染页面:- /// 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)
- };
- }
复制代码 这对创建骨架加载状态很有用,例如:- <h1>{data.post.title}</h1>
- {@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}
复制代码 在流式传输数据时,请注意正确处理 Promise rejections。具体来说,如果懒加载的 Promise 在渲染开始前失败(此时会被捕获)且没有以某种方式处理错误,服务器可能会因 "unhandled promise rejection" 错误而崩溃。
当在 load 函数中直接使用 SvelteKit 的 fetch 时,SvelteKit 会为您处理这种情况。对于其他 Promise,只需为 Promise 添加一个空的 catch 即可将其标记为已处理。- /// 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()
- };
- }
复制代码[!NOTE] 在不支持流式传输的平台上(如 AWS Lambda 或 Firebase),响应将被缓冲。这意味着页面只会在所有 promise resolve 后才会渲染。如果您使用代理(例如 NGINX),请确保它不会缓冲来自代理服务器的响应。
[!NOTE] 流式数据传输只有在启用 JavaScript 时才能工作。如果页面是服务端渲染的,您应该避免从通用 load 函数返回 promise,因为这些 promise 不会被流式传输 —— 相反,当函数在浏览器中重新运行时,promise 会被重新创建。
[!NOTE] 一旦响应开始流式传输,就无法更改响应的标头和状态码,因此您无法 setHeaders 或抛出重定向到流式 promise 内。
[!NOTE] 在 SvelteKit 1.x 中,顶层 promise 会自动 awaited,只有嵌套的 promise 才会流式传输。
并行加载
在渲染(或导航到)页面时,SvelteKit 会同时运行所有 load 函数,避免请求瀑布。在客户端导航期间,多个服务器 load 函数的调用结果会被组合到单个响应中。一旦所有 load 函数都返回结果,页面就会被渲染。
重新运行 load 函数
SvelteKit 会追踪每个 load 函数的依赖关系,以避免在导航过程中不必要的重新运行。
例如,给定一对这样的 load 函数...- /// 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)
- };
- }
复制代码- /// 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 函数也会重新运行。
依赖追踪在 load 函数返回后不再适用 — 例如,在嵌套的 promise 中访问 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 函数实现这一点:- /// 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!' };
- }
- }
复制代码 手动失效
你还可以使用 invalidate(url) 重新运行适用于当前页面的 load 函数,它会重新运行所有依赖于 url 的 load 函数,以及使用 invalidateAll() 重新运行每个 load 函数。服务端加载函数永远不会自动依赖于获取数据的 url,以避免将秘密泄露给客户端。
如果一个 load 函数调用了 fetch(url) 或 depends(url),那么它就依赖于 url。注意,url 可以是以 [a-z]开头的自定义标识符:- /// 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()
- };
- }
复制代码- <p>random number: {data.number}</p>
- <button onclick={rerunLoadFunction}>Update random number</button>
复制代码 load 函数何时重新运行?
总的来说,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() 并重新运行,且父函数是服务端 load 函数
- 它通过 fetch(仅限通用 load)或 depends 声明了对特定 URL 的依赖,且该 URL 被 invalidate(url) 标记为无效
- 所有活动的 load 函数都被 invalidateAll() 强制重新运行
params 和 url 可以在响应 链接点击、 交互goto 调用或 重定向 时发生变化。
注意,重新运行 load 函数将更新相应 +layout.svelte 或 +page.svelte 中的 data 属性;这不会导致组件重新创建。因此,内部状态会被保留。如果这不是你想要的,你可以在afterNavigate 回调中重置所需内容,或者用 {#key ...} 块包装你的组件。
对身份验证的影响
数据加载的几个特性对身份验证有重要影响:
- 布局 load 函数不会在每个请求时运行,例如在子路由之间的客户端导航期间。(load函数何时重新运行?)
- 布局和页面 load 函数会同时运行,除非调用了 await parent()。如果布局 load 抛出错误,页面 load 函数会运行,但客户端将不会收到返回的数据。
有几种可能的策略来确保在受保护代码之前进行身份验证检查。
为防止数据瀑布并保留布局 load 缓存:
- 使用 hooks 在任何 load 函数运行之前保护多个路由
- 在 +page.server.js load 函数中直接使用身份验证守卫进行特定路由保护
在 +layout.server.js 中放置身份验证守卫要求所有子页面在受保护代码之前调用 await parent()。除非每个子页面都依赖于await parent() 返回的数据,否则其他选项会更有性能优势。
拓展阅读
- 教程:数据加载
- 教程:错误和重定向
- 教程:高级加载
Svelte 中文文档
点击查看中文文档 - SvelteKit 数据加载。
系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!
此外我还写过 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答读者问等 14 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog
欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |