TanStack Router 的设计理念是并行运行加载器 (loader) 并等待所有加载器解析完成后再渲染下一个路由。大多数情况下这很理想,但有时您可能希望先向用户展示部分内容,同时让其他非关键数据在后台继续加载。
延迟数据加载 (Deferred data loading) 是一种模式,允许路由在渲染新位置的关键数据/标记时,让较慢的非关键路由数据在后台继续解析。该流程在客户端和服务端(通过流式传输)均可工作,是提升应用感知性能 (perceived performance) 的有效方式。
如果您使用 TanStack Query 或其他数据获取库,延迟数据加载的工作方式会有所不同。请直接跳转到 使用外部库实现延迟数据加载 章节了解更多信息。
要延迟加载较慢或非关键数据,只需在加载器响应中返回一个 未等待/未解析 的 Promise:
// src/routes/posts.$postId.tsx
import { createFileRoute, defer } from '@tanstack/react-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/react-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async () => {
// 获取较慢的数据但不等待
const slowDataPromise = fetchSlowData()
// 获取并等待快速解析的数据
const fastData = await fetchFastData()
return {
fastData,
deferredSlowData: slowDataPromise,
}
},
})
只要任意被等待的 Promise 解析完成,下一个路由就会开始渲染,同时延迟的 Promise 继续在后台解析。
在组件中,可以通过 Await 组件来解析和使用延迟的 Promise:
// src/routes/posts.$postId.tsx
import { createFileRoute, Await } from '@tanstack/react-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/react-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() 钩子。
Await 组件通过触发最近的悬念边界 (suspense boundary) 来解析 Promise,解析完成后会将组件 children 作为函数渲染,并传入解析后的数据。
如果 Promise 被拒绝,Await 组件会抛出序列化的错误,该错误可以被最近的错误边界捕获。
Tip
在 React 19 中,您可以使用 use() 钩子替代 Await
当您通过 TanStack Query 等外部库使用 外部数据加载 策略时,延迟数据加载的工作方式有所不同,因为这些库会在 TanStack Router 之外处理数据获取和缓存。
此时不需要使用 defer 和 Await,而应该通过路由的 loader 启动数据获取,然后在组件中使用库提供的钩子访问数据:
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/react-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/react-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/react-router'
import { useSuspenseQuery } from '@tanstack/react-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/react-router'
import { useSuspenseQuery } from '@tanstack/react-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 的生命周期与其关联的加载器数据相同,它们甚至可以被预加载!
流式传输需要服务端支持,并且 TanStack Router 需正确配置。
请完整阅读 流式 SSR 指南 了解服务端流式配置的逐步指导。
以下是 TanStack Router 延迟数据流式传输的高层工作流程:
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.