延迟数据加载

TanStack Router 的设计是在并行运行所有 loader,并在所有 loader 解析后再渲染下一个路由。这在大多数情况下都很好,但偶尔,您可能希望在后台加载其余数据时更早地向用户显示一些内容。

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

如果您正在使用 TanStack Query 或任何其他数据获取库,那么延迟数据加载的实现方式会略有不同。请跳至 使用外部库进行延迟数据加载 部分以获取更多信息。

使用 Await 进行延迟数据加载

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

tsx
// src/routes/posts.$postId.tsx
import { createFileRoute, defer } from '@tanstack/solid-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/solid-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/solid-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/solid-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() Hook 的访问。

Await 组件通过触发最近的 suspense 边界来解析 Promise,直到其被解析,然后它将以已解析的数据作为函数的 children 进行渲染。

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

使用外部库进行延迟数据加载

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

因此,您将需要使用路由的 loader 来启动数据获取,然后使用库的 Hooks 在组件中访问数据,而不是使用 deferAwait

tsx
// 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 } }) => {
    // 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/solid-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())
  },
})

然后在您的组件中,您可以使用库的 Hooks 来访问数据

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

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

  // do something with fastData

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

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

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

缓存和失效

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

我们的合作伙伴
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
订阅 Bytes

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

Bytes

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

订阅 Bytes

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

Bytes

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