零成本的型別安全
更方便、更準確,更少樣板程式碼
透過在您的 SvelteKit 應用程式中加入型別註釋,您可以獲得跨網路的完整型別安全 — 您頁面中的 data
具有從產生該資料的 load
函式的回傳值推斷出的型別,而您無需明確宣告任何內容。這就像是您會開始懷疑自己以前沒有它是怎麼過的。
但是,如果我們甚至不需要這些註釋呢?由於 load
和 data
是框架的一部分,框架難道不能為我們輸入它們嗎?畢竟,這就是電腦的用途 — 完成枯燥的部分,讓我們可以專注於創造性的工作。
截至今天,答案是肯定的:它可以。
如果您正在使用 VSCode,只需將 Svelte 擴充功能升級到最新版本,您就再也不需要註釋您的 load
函式或 data
屬性了。其他編輯器的擴充功能也可以使用此功能,只要它們支援語言伺服器協定和 TypeScript 外掛程式即可。它甚至適用於我們最新的 CLI 診斷工具 svelte-check
!
在我們深入探討之前,讓我們先回顧一下型別安全在 SvelteKit 中是如何運作的。
產生的型別
在 SvelteKit 中,您可以在 load
函式中取得頁面的資料。您可以透過使用 @sveltejs/kit
中的 ServerLoadEvent
來輸入事件
import type { interface ServerLoadEvent<Params extends Partial<Record<string, string>> = Partial<Record<string, string>>, ParentData extends Record<string, any> = Record<string, any>, RouteId extends string | null = string | null>
ServerLoadEvent } from '@sveltejs/kit';
export async function function load(event: ServerLoadEvent): Promise<{
post: string;
}>
load(event: ServerLoadEvent<Partial<Record<string, string>>, Record<string, any>, string | null>
event: interface ServerLoadEvent<Params extends Partial<Record<string, string>> = Partial<Record<string, string>>, ParentData extends Record<string, any> = Record<string, any>, RouteId extends string | null = string | null>
ServerLoadEvent) {
return {
post: string
post: await const database: {
getPost(slug: string | undefined): Promise<string>;
}
database.function getPost(slug: string | undefined): Promise<string>
getPost(event: ServerLoadEvent<Partial<Record<string, string>>, Record<string, any>, string | null>
event.RequestEvent<Partial<Record<string, string>>, string | null>.params: Partial<Record<string, string>>
The parameters of the current route - e.g. for a route like /blog/[slug]
, a { slug: string }
object
params.string | undefined
post)
};
}
這可行,但我們可以做得更好。請注意,我們不小心寫了 event.params.post
,即使參數名稱是 slug
(因為檔案名稱中的 [slug]
)而不是 post
。您可以透過將泛型引數加入 ServerLoadEvent
來自己輸入 params
,但這很脆弱。
這就是我們自動型別產生功能發揮作用的地方。每個路由目錄都有一個隱藏的 $types.d.ts
檔案,其中包含特定於路由的型別
import type { ServerLoadEvent } from '@sveltejs/kit';
import type { import PageServerLoadEvent
PageServerLoadEvent } from './$types';
export async function function load(event: PageServerLoadEvent): Promise<{
post: any;
}>
load(event: PageServerLoadEvent
event: import PageServerLoadEvent
PageServerLoadEvent) {
return {
post: await database.getPost(event.params.post)
post: any
post: await database.getPost(event: PageServerLoadEvent
event.params.slug)
};
}
這揭示了我們的拼寫錯誤,因為它現在在 params.post
屬性存取時發生錯誤。除了縮小參數型別之外,它還縮小了 await event.parent()
和從伺服器 load
函式傳遞到通用 load
函式的 data
的型別。請注意,我們現在使用 PageServerLoadEvent
,以將其與 LayoutServerLoadEvent
區分開來。
載入資料後,我們想要在 +page.svelte
中顯示它。相同的型別產生機制可確保 data
的型別正確
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
虛擬檔案
在執行開發伺服器或建置時,會自動產生型別。由於基於檔案系統的路由,SvelteKit 能夠透過遍歷路由樹來推斷正確的參數或父資料等內容。結果會輸出到每個路由的一個 $types.d.ts
檔案中,其大致如下
import type * as module "@sveltejs/kit"
Kit from '@sveltejs/kit';
// types inferred from the routing tree
type type RouteParams = {
slug: string;
}
RouteParams = { slug: string
slug: string };
type type RouteId = "/blog/[slug]"
RouteId = '/blog/[slug]';
type type PageParentData = {}
PageParentData = {};
// PageServerLoad type extends the generic Load type and fills its generics with the info we have
export type type PageServerLoad = (event: Kit.ServerLoadEvent<RouteParams, PageParentData, string | null>) => MaybePromise<"/blog/[slug]">
PageServerLoad = module "@sveltejs/kit"
Kit.type ServerLoad<Params extends Partial<Record<string, string>> = Partial<Record<string, string>>, ParentData extends Record<string, any> = Record<string, any>, OutputData extends Record<string, any> | void = void | Record<...>, RouteId extends string | null = string | null> = (event: Kit.ServerLoadEvent<Params, ParentData, RouteId>) => MaybePromise<OutputData>
The generic form of PageServerLoad
and LayoutServerLoad
. You should import those from ./$types
(see generated types)
rather than using ServerLoad
directly.
ServerLoad<type RouteParams = {
slug: string;
}
RouteParams, type PageParentData = {}
PageParentData, type RouteId = "/blog/[slug]"
RouteId>;
// The input parameter type of the load function
export type type PageServerLoadEvent = Kit.ServerLoadEvent<RouteParams, PageParentData, string | null>
PageServerLoadEvent = type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tuple
Parameters<type PageServerLoad = (event: Kit.ServerLoadEvent<RouteParams, PageParentData, string | null>) => MaybePromise<"/blog/[slug]">
PageServerLoad>[0];
// The return type of the load function
export type type PageData = Kit.ReturnType<any>
PageData = module "@sveltejs/kit"
Kit.type Kit.ReturnType = /*unresolved*/ any
ReturnType<
typeof import('../src/routes/blog/[slug]/+page.server.js').load
>;
我們實際上不會將 $types.d.ts
寫入您的 src
目錄中 — 那樣會很雜亂,而且沒人喜歡雜亂的程式碼。相反,我們使用一個名為 rootDirs
的 TypeScript 功能,它允許我們將「虛擬」目錄對應到真實目錄。透過將 rootDirs
設定為專案根目錄(預設值),並額外設定為 .svelte-kit/types
(所有產生型別的輸出資料夾),然後在其中鏡像路由結構,我們就可以獲得所需的行為
// on disk:
.svelte-kit/
├ types/
│ ├ src/
│ │ ├ routes/
│ │ │ ├ blog/
│ │ │ │ ├ [slug]/
│ │ │ │ │ └ $types.d.ts
src/
├ routes/
│ ├ blog/
│ │ ├ [slug]/
│ │ │ ├ +page.server.ts
│ │ │ └ +page.svelte
// what TypeScript sees:
src/
├ routes/
│ ├ blog/
│ │ ├ [slug]/
│ │ │ ├ $types.d.ts
│ │ │ ├ +page.server.ts
│ │ │ └ +page.svelte
無須型別的型別安全
由於自動型別產生,我們獲得了進階的型別安全。如果我們可以完全省略編寫型別,那不是很好嗎?從今天起,您就可以做到這一點
import type { PageServerLoadEvent } from './$types';
export async function function load(event: any): Promise<{
post: any;
}>
load(event: any
event: PageServerLoadEvent) {
return {
post: any
post: await database.getPost(event: any
event.params.post)
};
}
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
export let data;
</script>
雖然這非常方便,但不僅僅如此。它還關係到正確性:複製和貼上程式碼時,很容易不小心將 PageServerLoadEvent
與 LayoutServerLoadEvent
或 PageLoadEvent
混淆 — 具有細微差異的類似型別。Svelte 的主要見解是,透過以宣告式的方式編寫程式碼,我們可以讓機器為我們完成大部分工作,正確且高效地完成。這沒有什麼不同 — 透過利用強大的框架慣例(例如 +page
檔案),我們可以更輕鬆地做正確的事,而不是做錯事。
這適用於 SvelteKit 檔案中的所有匯出(+page
、+layout
、+server
、hooks
、params
等),以及 +page/layout.svelte
檔案中的 data
、form
和 snapshot
屬性。
若要在 VS Code 中使用此功能,請安裝最新版本的 Svelte for VS Code 擴充功能。對於其他 IDE,請使用最新版本的 Svelte 語言伺服器和 Svelte TypeScript 外掛程式。除了編輯器之外,我們的命令列工具 svelte-check
自 3.1.1 版起也知道如何新增這些註釋。
它是如何運作的?
要使其正常運作,需要對語言伺服器(為 Svelte 檔案中的 IntelliSense 提供支援)和 TypeScript 外掛程式(使 TypeScript 了解 .ts/js
檔案中的 Svelte 檔案)進行變更。在這兩者中,我們都會在正確的位置自動插入正確的型別,並告知 TypeScript 使用我們的虛擬增強檔案,而不是原始的無型別檔案。這與來回映射產生的位置和原始位置相結合,即可獲得所需的結果。由於 svelte-check
在幕後重複使用語言伺服器的部分,因此它可以免費獲得該功能,而無需進一步調整。
我們要感謝 Next.js 團隊 啟發 了此功能。
下一步
未來,我們希望研究使 SvelteKit 的更多區域具有型別安全性 — 例如連結,無論是在您的 HTML 中還是透過程式方式呼叫 goto
。
TypeScript 正在蠶食 JavaScript 世界 — 我們很樂意看到這種情況!我們非常關心 SvelteKit 中的一流型別安全,並且我們為您提供工具,使體驗盡可能順暢 — 無論您是透過 JSDoc 使用 TypeScript 還是類型化的 JavaScript,都能完美地擴展到更大的 Svelte 程式碼庫。