框架
版本

延迟数据加载

TanStack Router 旨在并行运行加载器并等待所有加载器解析后再渲染下一个路由。大多数情况下这都很好,但偶尔你可能希望在其余数据在后台加载时提前向用户展示一些内容。

延迟数据加载是一种模式,它允许路由器渲染下一个位置的关键数据/标记,同时较慢的非关键路由数据在后台解析。此过程在客户端和服务器(通过流式传输)上都有效,是提高应用程序感知性能的好方法。

如果你正在使用 TanStack Query 或任何其他数据获取库,那么延迟数据加载的工作方式有所不同。请跳到使用外部库延迟数据加载部分以获取更多信息。

使用 Await 延迟数据加载

要延迟缓慢或非关键数据,请在加载器响应中的任何位置返回一个未等待/未解析的 Promise

tsx
// src/routes/posts.$postId.tsx
import { createFileRoute, defer } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({
  loader: async () => {
    // Fetch some slower data, but do not await it
    const slowDataPromise = fetchSlowData()

    // Fetch and await some data that resolves quickly
    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 () => {
    // Fetch some slower data, but do not await it
    const slowDataPromise = fetchSlowData()

    // Fetch and await some data that resolves quickly
    const fastData = await fetchFastData()

    return {
      fastData,
      deferredSlowData: slowDataPromise,
    }
  },
})

一旦任何已等待的 Promise 得到解析,下一个路由将开始渲染,而延迟的 Promise 则继续解析。

在组件中,可以使用 Await 组件解析和利用延迟的 Promise

tsx
// 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()

  // do something with 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()

  // do something with fastData

  return (
    <Await promise={deferredSlowData} fallback={<div>Loading...</div>}>
      {(data) => {
        return <div>{data}</div>
      }}
    </Await>
  )
}

提示

如果你的组件是代码分割的,你可以使用 getRouteApi 函数,以避免导入 Route 配置来访问类型化的 useLoaderData() 钩子。

Await 组件通过触发最近的 Suspense 边界来解析 Promise,直到它被解析,之后它将组件的 children 渲染为一个函数,并带有所解析的数据。

如果 Promise 被拒绝,Await 组件将抛出序列化的错误,该错误可以被最近的错误边界捕获。

提示

在 React 19 中,你可以使用 use() 钩子而不是 Await

使用外部库延迟数据加载

当您为路由获取信息时,依赖于使用 外部数据加载TanStack Query 等外部库时,延迟数据加载的工作方式会略有不同,因为该库在 TanStack Router 之外为您处理数据获取和缓存。

因此,您不需要使用 deferAwait,而是应该使用路由的 loader 来启动数据获取,然后使用库的钩子来访问组件中的数据。

tsx
// 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 } }) => {
    // Kick off the fetching of some slower data, but do not await it
    queryClient.prefetchQuery(slowDataOptions())

    // Fetch and await some data that resolves quickly
    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 } }) => {
    // Kick off the fetching of some slower data, but do not await it
    queryClient.prefetchQuery(slowDataOptions())

    // Fetch and await some data that resolves quickly
    await queryClient.ensureQueryData(fastDataOptions())
  },
})

然后,在你的组件中,你可以使用库的钩子来访问数据

tsx
// 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())

  // do something with 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())

  // do something with fastData

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <SlowDataComponent />
    </Suspense>
  )
}

function SlowDataComponent() {
  const data = useSuspenseQuery(slowDataOptions())

  return <div>{data}</div>
}

缓存和失效

流式 Promise 遵循与其关联的加载器数据相同的生命周期。它们甚至可以预加载!

SSR 和流式延迟数据

流式传输需要支持它的服务器,并且需要正确配置 TanStack Router 才能使用它。

请阅读完整的 流式 SSR 指南,了解如何设置服务器进行流式传输的分步说明。

SSR 流式生命周期

以下是 TanStack Router 如何处理延迟数据流的高级概述

  • 服务器
    • 从路由加载器返回的 Promise 会被标记和跟踪
    • 所有加载器解析后,所有延迟的 Promise 都会被序列化并嵌入到 HTML 中
    • 路由开始渲染
    • 使用 <Await> 组件渲染的延迟 Promise 会触发 Suspense 边界,允许服务器将 HTML 流式传输到该点
  • 客户端
    • 客户端从服务器接收初始 HTML
    • <Await> 组件在等待数据在服务器上解析时,会使用占位符 Promise 挂起
  • 服务器
    • 当延迟的 Promise 解析后,它们的结果(或错误)会通过内联脚本标签序列化并流式传输到客户端
    • 解析后的 <Await> 组件及其 Suspense 边界会得到解析,其生成的 HTML 会与脱水数据一起流式传输到客户端
  • 客户端
    • <Await> 中挂起的占位符 Promise 会用流式传输的数据/错误响应解析,并渲染结果或将错误抛给最近的错误边界
我们的合作伙伴
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。

订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。