延迟数据加载

TanStack Router 设计为并行运行加载器,并等待所有加载器解析完成后再渲染下一个路由。这在大多数情况下都很好,但有时,您可能希望在其余数据在后台加载的同时更快地向用户展示一些内容。

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

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

使用 Await 进行延迟数据加载

要延迟加载慢速或非关键数据,在加载器响应中返回一个未等待/未解析的 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>
  )
}

Tip

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

Await 组件通过触发最近的 suspense 边界来解析 promise,直到它被解析完成,之后它将组件的 children 渲染为带有解析数据的函数。

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

使用外部库的延迟数据加载

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

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

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())
  },
})

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

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 遵循与其关联的加载器数据相同的生命周期。它们甚至可以被预加载!