TanStack Router 的設計是讓載入器 (loader) 並行執行,並等待所有載入器解析完成後才渲染下一個路由。這在大多數情況下運作良好,但有時你可能希望先向使用者顯示部分內容,同時讓其他資料在背景繼續載入。
延遲資料載入 (Deferred data loading) 是一種模式,允許路由在較慢的非關鍵路由資料於背景解析時,先渲染下個位置 (location) 的關鍵資料/標記 (markup)。此流程在客戶端和伺服器端 (透過串流 streaming) 均可運作,是提升應用程式感知效能 (perceived performance) 的好方法。
如果你使用像 TanStack Query 或其他資料獲取函式庫,延遲資料載入的運作方式會有些不同。請跳至 使用外部函式庫的延遲資料載入 章節了解更多資訊。
要延遲載入較慢或非關鍵的資料,可在你的載入器 (loader) 回應中回傳一個 未等待/未解析 (unawaited/unresolved) 的 promise:
// src/routes/posts.$postId.tsx
import { createFileRoute, defer } from '@tanstack/solid-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async () => {
// 獲取較慢的資料,但不等待它
const slowDataPromise = fetchSlowData()
// 獲取並等待一些能快速解析的資料
const fastData = await fetchFastData()
return {
fastData,
deferredSlowData: slowDataPromise,
}
},
})
// src/routes/posts.$postId.tsx
import { createFileRoute, defer } from '@tanstack/solid-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async () => {
// 獲取較慢的資料,但不等待它
const slowDataPromise = fetchSlowData()
// 獲取並等待一些能快速解析的資料
const fastData = await fetchFastData()
return {
fastData,
deferredSlowData: slowDataPromise,
}
},
})
一旦任何被等待 (awaited) 的 promise 解析完成,下個路由就會開始渲染,同時延遲的 promise 會繼續解析。
在元件中,可以使用 Await 元件來解析並使用延遲的 promise:
// src/routes/posts.$postId.tsx
import { createFileRoute, Await } from '@tanstack/solid-router'
export const Route = createFileRoute('/posts/$postId')({
// ...
component: PostIdComponent,
})
function PostIdComponent() {
const { deferredSlowData, fastData } = Route.useLoaderData()
// 對 fastData 進行一些操作
return (
<Await promise={deferredSlowData} fallback={<div>Loading...</div>}>
{(data) => {
return <div>{data}</div>
}}
</Await>
)
}
// src/routes/posts.$postId.tsx
import { createFileRoute, Await } from '@tanstack/solid-router'
export const Route = createFileRoute('/posts/$postId')({
// ...
component: PostIdComponent,
})
function PostIdComponent() {
const { deferredSlowData, fastData } = Route.useLoaderData()
// 對 fastData 進行一些操作
return (
<Await promise={deferredSlowData} fallback={<div>Loading...</div>}>
{(data) => {
return <div>{data}</div>
}}
</Await>
)
}
Tip
如果你的元件是程式碼分割 (code-split) 的,可以使用 getRouteApi 函式 來避免導入 Route 配置,以獲取型別化的 useLoaderData() 鉤子 (hook)。
Await 元件會透過觸發最近的 suspense 邊界 (boundary) 來解析 promise,直到它被解析後,才會將元件的 children 作為函式渲染,並傳入解析後的資料。
如果 promise 被拒絕 (rejected),Await 元件會拋出序列化的錯誤,這可以被最近的錯誤邊界 (error boundary) 捕獲。
當你的路由資訊獲取策略依賴於 外部資料載入 (External Data Loading) 並使用像 TanStack Query 這樣的外部函式庫時,延遲資料載入的運作方式會有些不同,因為函式庫會在 TanStack Router 之外為你處理資料獲取和快取。
因此,與其使用 defer 和 Await,你應該使用路由的 loader 來啟動資料獲取,然後在元件中使用函式庫的鉤子 (hook) 來存取資料。
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { slowDataOptions, fastDataOptions } from '~/api/query-options'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ context: { queryClient } }) => {
// 啟動較慢資料的獲取,但不等待它
queryClient.prefetchQuery(slowDataOptions())
// 獲取並等待一些能快速解析的資料
await queryClient.ensureQueryData(fastDataOptions())
},
})
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { slowDataOptions, fastDataOptions } from '~/api/query-options'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ context: { queryClient } }) => {
// 啟動較慢資料的獲取,但不等待它
queryClient.prefetchQuery(slowDataOptions())
// 獲取並等待一些能快速解析的資料
await queryClient.ensureQueryData(fastDataOptions())
},
})
然後在你的元件中,可以使用函式庫的鉤子 (hook) 來存取資料:
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { useSuspenseQuery } from '@tanstack/solid-query'
import { slowDataOptions, fastDataOptions } from '~/api/query-options'
export const Route = createFileRoute('/posts/$postId')({
// ...
component: PostIdComponent,
})
function PostIdComponent() {
const fastData = useSuspenseQuery(fastDataOptions())
// 對 fastData 進行一些操作
return (
<Suspense fallback={<div>Loading...</div>}>
<SlowDataComponent />
</Suspense>
)
}
function SlowDataComponent() {
const data = useSuspenseQuery(slowDataOptions())
return <div>{data}</div>
}
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { useSuspenseQuery } from '@tanstack/solid-query'
import { slowDataOptions, fastDataOptions } from '~/api/query-options'
export const Route = createFileRoute('/posts/$postId')({
// ...
component: PostIdComponent,
})
function PostIdComponent() {
const fastData = useSuspenseQuery(fastDataOptions())
// 對 fastData 進行一些操作
return (
<Suspense fallback={<div>Loading...</div>}>
<SlowDataComponent />
</Suspense>
)
}
function SlowDataComponent() {
const data = useSuspenseQuery(slowDataOptions())
return <div>{data}</div>
}
串流的 promise 遵循與其關聯的載入器資料相同的生命週期。它們甚至可以被預載 (preloaded)!
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.