译零 TransZero · TanStack Query 中文文档
主站

高级服务端渲染

更新于 2026-11-02 React 查看英文原文 ↗

欢迎来到高级服务端渲染指南,你将在此全面了解如何在流式传输(streaming)、服务端组件(Server Components)以及 Next.js 应用路由器(app router)中使用 React Query。

在阅读本指南前,你可能希望先阅读 服务端渲染与注水指南,它讲解了在服务端渲染(SSR)中使用 React Query 的基础知识;此外,性能与请求瀑布 和 预取与路由集成 也包含有价值的背景知识。

开始之前,请注意:SSR 指南中所述的 initialData 方法在服务端组件中同样适用,但本指南将聚焦于注水(hydration)相关 API。

服务端组件与 Next.js 应用路由器

我们不会在此深入探讨服务端组件,但简而言之,它们是保证仅在服务端运行的组件——不仅在初始页面加载时如此,在页面跳转时亦然。这类似于 Next.js getServerSideProps/getStaticProps 和 Remix loader 的工作方式:它们也始终在服务端运行,但仅能返回数据;而服务端组件能力更广。不过,对 React Query 而言,数据部分仍是核心,因此我们聚焦于此。

我们如何将服务端渲染指南中所学的 在框架加载器(loader)中预取数据并传入应用 这一方法,迁移到服务端组件和 Next.js 应用路由器中?思考这个问题的最佳起点,是把服务端组件视作“仅仅”另一种框架加载器。

关于术语的一点说明

到目前为止,这些指南中我们一直在讨论 服务端 和 客户端。需要特别注意的是,这种划分并不严格对应于 服务端组件 和 客户端组件。服务端组件确实保证只在服务端运行,但客户端组件实际上可在两个地方运行:因为它们也可能在初始 服务端渲染 阶段进行渲染。

一种理解方式是:尽管服务端组件也会 渲染,但其发生在“加载器阶段”(始终在服务端),而客户端组件则运行于“应用阶段”。该应用既可在服务端(SSR 期间)运行,也可在例如浏览器中运行。该应用具体运行位置、是否参与 SSR,可能因框架而异。

初始设置

任何 React Query 设置的第一步,始终是创建一个 queryClient 并用 QueryClientProvider 包裹你的应用。在服务端组件中,此步骤在各框架间大体相同,主要差异在于文件命名约定:

TypeScript
1// In Next.js, this file would be called: app/providers.tsx2'use client'3 4// Since QueryClientProvider relies on useContext under the hood, we have to put 'use client' on top5import {6  environmentManager,7  QueryClient,8  QueryClientProvider,9} from '@tanstack/react-query'10 11function makeQueryClient() {12  return new QueryClient({13    defaultOptions: {14      queries: {15        // With SSR, we usually want to set some default staleTime16        // above 0 to avoid refetching immediately on the client17        staleTime: 60 * 1000,18      },19    },20  })21}22 23let browserQueryClient: QueryClient | undefined = undefined24 25function getQueryClient() {26  if (environmentManager.isServer()) {27    // Server: always make a new query client28    return makeQueryClient()29  } else {30    // Browser: make a new query client if we don't already have one31    // This is very important, so we don't re-make a new client if React32    // suspends during the initial render. This may not be needed if we33    // have a suspense boundary BELOW the creation of the query client34    if (!browserQueryClient) browserQueryClient = makeQueryClient()35    return browserQueryClient36  }37}38 39export default function Providers({ children }: { children: React.ReactNode }) {40  // NOTE: Avoid useState when initializing the query client if you don't41  //       have a suspense boundary between this and the code that may42  //       suspend because React will throw away the client on the initial43  //       render if it suspends and there is no boundary44  const queryClient = getQueryClient()45 46  return (47    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>48  )49}
TypeScript
1// In Next.js, this file would be called: app/layout.tsx2import Providers from './providers'3 4export default function RootLayout({5  children,6}: {7  children: React.ReactNode8}) {9  return (10    <html lang="en">11      <head />12      <body>13        <Providers>{children}</Providers>14      </body>15    </html>16  )17}

这部分与 SSR 指南中的做法非常相似,我们只需将内容拆分为两个不同文件即可。

预取与脱水/注水数据

接下来,我们来看如何实际预取数据,然后脱水(dehydrate)和注水(hydrate)它。使用 Next.js Pages Router 时,代码如下所示:

TypeScript
1// pages/posts.tsx2import {3  dehydrate,4  HydrationBoundary,5  QueryClient,6  useQuery,7} from '@tanstack/react-query'8 9// This could also be getServerSideProps10export async function getStaticProps() {11  const queryClient = new QueryClient()12 13  await queryClient14    .query({15      queryKey: ['posts'],16      queryFn: getPosts,17    })18    .catch(noop)19 20  return {21    props: {22      dehydratedState: dehydrate(queryClient),23    },24  }25}26 27function Posts() {28  // This useQuery could just as well happen in some deeper child to29  // the <PostsRoute>, data will be available immediately either way30  //31  // Note that we are using useQuery here instead of useSuspenseQuery.32  // Because this data has already been prefetched, there is no need to33  // ever suspend in the component itself. If we forget or remove the34  // prefetch, this will instead fetch the data on the client, while35  // using useSuspenseQuery would have had worse side effects.36  const { data } = useQuery({ queryKey: ['posts'], queryFn: getPosts })37 38  // This query was not prefetched on the server and will not start39  // fetching until on the client, both patterns are fine to mix40  const { data: commentsData } = useQuery({41    queryKey: ['posts-comments'],42    queryFn: getComments,43  })44 45  // ...46}47 48export default function PostsRoute({ dehydratedState }) {49  return (50    <HydrationBoundary state={dehydratedState}>51      <Posts />52    </HydrationBoundary>53  )54}

转换为应用路由器后,代码结构其实非常相似,只需稍作调整。首先,我们创建一个服务端组件来完成预取部分:

TypeScript
1// app/posts/page.tsx2import {3  dehydrate,4  HydrationBoundary,5  QueryClient,6} from '@tanstack/react-query'7import Posts from './posts'8 9export default async function PostsPage() {10  const queryClient = new QueryClient()11 12  await queryClient13    .query({14      queryKey: ['posts'],15      queryFn: getPosts,16    })17    .catch(noop)18 19  return (20    // Neat! Serialization is now as easy as passing props.21    // HydrationBoundary is a Client Component, so hydration will happen there.22    <HydrationBoundary state={dehydrate(queryClient)}>23      <Posts />24    </HydrationBoundary>25  )26}

接着,我们来看客户端组件部分的写法:

TypeScript
1// app/posts/posts.tsx2'use client'3 4export default function Posts() {5  // This useQuery could just as well happen in some deeper6  // child to <Posts>, data will be available immediately either way7  const { data } = useQuery({8    queryKey: ['posts'],9    queryFn: () => getPosts(),10  })11 12  // This query was not prefetched on the server and will not start13  // fetching until on the client, both patterns are fine to mix.14  const { data: commentsData } = useQuery({15    queryKey: ['posts-comments'],16    queryFn: getComments,17  })18 19  // ...20}

上述示例的一个巧妙之处在于:其中唯一 Next.js 特有的部分仅为文件名,其余所有内容在任何支持服务端组件的其他框架中均保持一致。

在 SSR 指南中,我们提到你可以通过避免在每个路由中重复书写 <HydrationBoundary> 来消除样板代码。但在服务端组件中,这是不可能的。

注意:若你在 TypeScript 版本低于 5.1.3 且 @types/react 版本低于 18.2.8 的环境下使用异步服务端组件时遇到类型错误,建议升级至这两个工具的最新版本。或者,你可采用临时变通方案:在另一组件内调用该组件时添加 {/* @ts-expect-error Server Component */}。更多信息请参阅 Next.js TypeScript 文档中的 异步服务端组件 TypeScript 错误。

警告:我们不推荐在 queryFn 中使用 Next.js 服务端操作(Server Actions)来 拉取 数据。当从客户端调用时,服务端操作 按顺序执行,而非并行,这与 React Query 拉取和重新拉取查询的方式相冲突。这可能导致查询卡在等待中(pending)状态,或导致操作根本无法执行(参见 #7934)。向 queryFn 传递服务端操作引用也可能因 Only plain objects, and a few built-ins, can be passed to Server Actions... 失败,因为你必须 调用 该操作,而非仅传递其引用(参见 #6264)。如需在客户端拉取数据,请改用 API 路由中的 fetch,或使用 tRPC 等 RPC 层。服务端操作仍非常适合用于 修改(useMutation)。

嵌套服务端组件

服务端组件的一大优势是可嵌套,并能在 React 树中多层存在,从而允许你在数据实际使用处附近预取数据,而非仅限于应用顶层(正如 Remix 加载器那样)。这可以简单到一个服务端组件渲染另一个服务端组件(为简洁起见,本例中省略客户端组件):

TypeScript
1// app/posts/page.tsx2import {3  dehydrate,4  HydrationBoundary,5  QueryClient,6} from '@tanstack/react-query'7import Posts from './posts'8import CommentsServerComponent from './comments-server'9 10export default async function PostsPage() {11  const queryClient = new QueryClient()12 13  await queryClient14    .query({15      queryKey: ['posts'],16      queryFn: getPosts,17    })18    .catch(noop)19 20  return (21    <HydrationBoundary state={dehydrate(queryClient)}>22      <Posts />23      <CommentsServerComponent />24    </HydrationBoundary>25  )26}27 28// app/posts/comments-server.tsx29import {30  dehydrate,31  HydrationBoundary,32  QueryClient,33} from '@tanstack/react-query'34import Comments from './comments'35 36export default async function CommentsServerComponent() {37  const queryClient = new QueryClient()38 39  await queryClient40    .query({41      queryKey: ['posts-comments'],42      queryFn: getComments,43    })44    .catch(noop)45 46  return (47    <HydrationBoundary state={dehydrate(queryClient)}>48      <Comments />49    </HydrationBoundary>50  )51}

如你所见,在多个位置使用 <HydrationBoundary> 完全可行,你也可以为预取创建并脱水多个 queryClient。

请注意:由于我们在渲染 CommentsServerComponent 前 await getPosts,这将导致服务端请求瀑布:

Code
11. |> getPosts()22.   |> getComments()

如果服务端访问数据的延迟较低,这或许问题不大,但仍值得指出。

在 Next.js 中,除在 page.tsx 中预取数据外,你还可以在 layout.tsx 及 并行路由 中进行。由于这些都属于路由系统的一部分,Next.js 知道如何并行获取全部数据。因此,若上述 CommentsServerComponent 改为以并行路由形式表达,则请求瀑布将自动展平。

随着更多框架开始支持服务端组件,它们可能采用其他路由约定。请查阅你所用框架的文档以获取详情。

替代方案:使用单个 queryClient 进行预取

在上述示例中,我们为每个拉取数据的服务端组件创建了一个新的 queryClient。这是推荐做法;但如果你愿意,也可选择创建一个供所有服务端组件复用的单一 queryClient:

TypeScript
1// app/getQueryClient.tsx2import { QueryClient } from '@tanstack/react-query'3import { cache } from 'react'4 5// cache() is scoped per request, so we don't leak data between requests6const getQueryClient = cache(() => new QueryClient())7export default getQueryClient

这样做的好处是,你可在任何由服务端组件调用的位置(包括工具函数)中通过 getQueryClient() 获取该客户端实例。缺点是每次调用 dehydrate(getQueryClient()) 时,你都会序列化 整个 queryClient,包括那些此前已序列化过、且与当前服务端组件无关的查询,造成不必要的开销。

Next.js 已对使用 fetch() 的请求进行了去重(dedupe),但若你在 queryFn 中使用其他机制,或所用框架未自动去重此类请求,则采用上述单个 queryClient 方案可能更合理,尽管存在重复序列化问题。

作为未来改进方向,我们可能会开发一个 dehydrateNew() 函数(名称待定),该函数仅脱水自上次调用 dehydrateNew() 以来 新增 的查询。如果你对此感兴趣并愿意参与贡献,请随时联系我们!

数据所有权与重新验证

使用服务端组件时,思考数据所有权与重新验证(revalidation)至关重要。为解释原因,我们来看一个经修改的前述示例:

TypeScript
1// app/posts/page.tsx2import {3  dehydrate,4  HydrationBoundary,5  QueryClient,6} from '@tanstack/react-query'7import Posts from './posts'8 9export default async function PostsPage() {10  const queryClient = new QueryClient()11 12  // Note we are getting the result from query13  const posts = await queryClient.query({14    queryKey: ['posts'],15    queryFn: getPosts,16  })17 18  return (19    <HydrationBoundary state={dehydrate(queryClient)}>20      {/* This is the new part */}21      <div>Nr of posts: {posts.length}</div>22      <Posts />23    </HydrationBoundary>24  )25}

我们现在同时在服务端组件和客户端组件中渲染 getPosts 查询的数据。初始页面渲染时一切正常,但当 staleTime 已传递、而查询因某种原因在客户端重新验证时,会发生什么?

React Query 完全不知道如何 重新验证服务端组件。因此,若它在客户端重新拉取数据,导致 React 重新渲染帖子列表,则 Nr of posts: {posts.length} 将最终与之不同步。

若你设置了 staleTime: Infinity,使 React Query 永不重新验证,则此情况尚可接受;但若你最初就选用 React Query,这很可能并非你所需。

将 React Query 与服务端组件结合使用最有意义的场景包括:

  • 你有一个已使用 React Query 的应用,希望迁移到服务端组件,而无需重写全部数据拉取逻辑;
  • 你希望沿用熟悉的编程范式,同时在最合适的场景中融入服务端组件的优势;
  • 你有某些 React Query 能覆盖、但所选框架本身无法覆盖的用例。

何时将 React Query 与服务端组件配对使用才合理,很难给出普适性建议。如果你正从零开始构建一个全新的服务端组件应用,我们建议你优先使用框架自身提供的数据拉取工具,并避免过早引入 React Query,直到你真正需要它为止。 这种情况可能永远不会发生,这完全没问题——请选用最适合任务的工具!

若你确实使用它,一条实用经验法则是:避免在服务端渲染 queryClient.query 的结果,或将其传递给其他组件(即使是客户端组件)。

从 React Query 角度看,应将服务端组件仅视为预取数据的场所,别无他用。

当然,服务端组件拥有部分数据、客户端组件拥有其他数据完全可行,只需确保这两套现实不致脱节即可。

使用服务端组件进行流式传输

Next.js 应用路由器会自动将应用中任何已准备就绪的部分尽快流式传输(stream)至浏览器,从而使已完成内容可立即显示,无需等待仍在处理中的内容。此过程沿 <Suspense> 边界进行。请注意:若你创建一个 loading.tsx 文件,系统将在后台自动创建一个 <Suspense> 边界。

借助上述预取模式,React Query 完全兼容此类流式传输。每当每个 Suspense 边界的数据解析完成,Next.js 即可渲染并将完成内容流式传输至浏览器。即使你如上所述使用 useQuery,此机制依然有效,因为挂起(suspending)实际发生在你 await 预取时。

自 React Query v5.40.0 起,要实现此功能,你不再需要 await 所有预取操作,因为 pending 查询也可被脱水并发送至客户端。这让你能尽早启动预取,同时避免阻塞整个 Suspense 边界,并在查询完成时将 数据 流式传输至客户端。例如,当你想预取仅在用户交互后才可见的内容,或想 await 并渲染无限查询的第一页、同时在不阻塞渲染的前提下开始预取第二页时,此功能便十分有用。

为实现此功能,我们需要指示 queryClient 同时 dehydrate 等待中(pending)的查询。我们可通过全局配置,或直接将该选项传给 dehydrate 实现。

我们还需将 getQueryClient() 函数移出 app/providers.tsx 文件,因为我们希望在服务端组件及客户端提供器(client provider)中均能使用它。

TypeScript
1// app/get-query-client.ts2import {3  environmentManager,4  QueryClient,5  defaultShouldDehydrateQuery,6} from '@tanstack/react-query'7 8function makeQueryClient() {9  return new QueryClient({10    defaultOptions: {11      queries: {12        staleTime: 60 * 1000,13      },14      dehydrate: {15        // include pending queries in dehydration16        shouldDehydrateQuery: (query) =>17          defaultShouldDehydrateQuery(query) ||18          query.state.status === 'pending',19        shouldRedactErrors: (error) => {20          // We should not catch Next.js server errors21          // as that's how Next.js detects dynamic pages22          // so we cannot redact them.23          // Next.js also automatically redacts errors for us24          // with better digests.25          return false26        },27      },28    },29  })30}31 32let browserQueryClient: QueryClient | undefined = undefined33 34export function getQueryClient() {35  if (environmentManager.isServer()) {36    // Server: always make a new query client37    return makeQueryClient()38  } else {39    // Browser: make a new query client if we don't already have one40    // This is very important, so we don't re-make a new client if React41    // suspends during the initial render. This may not be needed if we42    // have a suspense boundary BELOW the creation of the query client43    if (!browserQueryClient) browserQueryClient = makeQueryClient()44    return browserQueryClient45  }46}

注意:此功能在 Next.js 和服务端组件中可用,是因为 React 在将 Promise 传递给客户端组件时,可跨网络序列化它们。

随后,我们只需提供一个 HydrationBoundary,而无需再 await 预取操作:

TypeScript
1// app/posts/page.tsx2import { dehydrate, HydrationBoundary } from '@tanstack/react-query'3import { getQueryClient } from './get-query-client'4import Posts from './posts'5 6// the function doesn't need to be `async` because we don't `await` anything7export default function PostsPage() {8  const queryClient = getQueryClient()9 10  // look ma, no await11  void queryClient12    .query({13      queryKey: ['posts'],14      queryFn: getPosts,15    })16    .catch(noop)17 18  return (19    <HydrationBoundary state={dehydrate(queryClient)}>20      <Posts />21    </HydrationBoundary>22  )23}

在客户端,Promise 将被自动置入 QueryCache。这意味着我们现在可在 Posts 组件内调用 useSuspenseQuery,以“使用”该 Promise(其在服务端创建):

TypeScript
1// app/posts/posts.tsx2'use client'3 4export default function Posts() {5  const { data } = useSuspenseQuery({ queryKey: ['posts'], queryFn: getPosts })6 7  // ...8}

注意:你也可使用 useQuery 替代 useSuspenseQuery,Promise 仍将被正确捕获。但此时 Next.js 不会挂起(suspend),组件将以 pending 状态渲染,这也意味着内容将退出服务端渲染。

若你使用非 JSON 数据类型并在服务端序列化查询结果,可指定 dehydrate.serializeData 和 hydrate.deserializeData 选项,以在边界两侧分别序列化和反序列化数据,确保缓存中的数据在服务端和客户端格式一致:

TypeScript
1// app/get-query-client.ts2import { QueryClient, defaultShouldDehydrateQuery } from '@tanstack/react-query'3import { deserialize, serialize } from './transformer'4 5function makeQueryClient() {6  return new QueryClient({7    defaultOptions: {8      // ...9      hydrate: {10        deserializeData: deserialize,11      },12      dehydrate: {13        serializeData: serialize,14      },15    },16  })17}18 19// ...
TypeScript
1// app/posts/page.tsx2import {3  dehydrate,4  HydrationBoundary,5  QueryClient,6} from '@tanstack/react-query'7import { getQueryClient } from './get-query-client'8import { serialize } from './transformer'9import Posts from './posts'10 11export default function PostsPage() {12  const queryClient = getQueryClient()13 14  // look ma, no await15  void queryClient16    .query({17      queryKey: ['posts'],18      queryFn: () => getPosts().then(serialize), // <-- serialize the data on the server19    })20    .catch(noop)21 22  return (23    <HydrationBoundary state={dehydrate(queryClient)}>24      <Posts />25    </HydrationBoundary>26  )27}
TypeScript
1// app/posts/posts.tsx2'use client'3 4export default function Posts() {5  const { data } = useSuspenseQuery({ queryKey: ['posts'], queryFn: getPosts })6 7  // ...8}

现在,你的 getPosts 函数可返回例如 Temporal datetime 对象,数据将在客户端被序列化和反序列化(前提是你的转换器支持这些数据类型的序列化与反序列化)。

更多信息,请参阅 带预取功能的 Next.js 应用示例。

在流式传输中使用持久化适配器(Persist Adapter)

若你将持久化适配器与 服务端组件流式传输 功能一同使用,需谨慎避免将 Promise 保存至存储。由于等待中查询可被脱水并流式传输至客户端,你应将持久化器(persister)配置为仅持久化成功查询:

TypeScript
1<PersistQueryClientProvider2  client={queryClient}3  persistOptions={{4    persister,5    // We don't want to save promises into the storage, so we only persist successful queries6    dehydrateOptions: { shouldDehydrateQuery: defaultShouldDehydrateQuery },7  }}8>9  {children}10</PersistQueryClientProvider>

此举确保仅有成功解析的查询被持久化至存储,防止因等待中 Promise 引发的序列化问题。

Next.js 中实验性无预取流式传输

虽然我们推荐上述预取方案,因其可在初始页面加载 及 后续任意页面导航中均消除请求瀑布,但也存在一种实验性方式:完全跳过预取,仍实现流式 SSR —— @tanstack/react-query-next-experimental

该包允许你在客户端组件中直接调用 useSuspenseQuery,即可在服务端拉取数据。随后,结果将随 SuspenseBoundary 解析而从服务端流式传输至客户端。若你调用 useSuspenseQuery 时未将其包裹在 <Suspense> 边界内,则 HTML 响应将一直等到拉取完成才开始发送。根据具体情况,这可能是你想要的效果,但请注意这将损害首字节时间(TTFB)。

要实现此功能,请用 ReactQueryStreamedHydration 组件包裹你的应用:

TypeScript
1// app/providers.tsx2'use client'3 4import {5  environmentManager,6  QueryClient,7  QueryClientProvider,8} from '@tanstack/react-query'9import * as React from 'react'10import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'11 12function makeQueryClient() {13  return new QueryClient({14    defaultOptions: {15      queries: {16        // With SSR, we usually want to set some default staleTime17        // above 0 to avoid refetching immediately on the client18        staleTime: 60 * 1000,19      },20    },21  })22}23 24let browserQueryClient: QueryClient | undefined = undefined25 26function getQueryClient() {27  if (environmentManager.isServer()) {28    // Server: always make a new query client29    return makeQueryClient()30  } else {31    // Browser: make a new query client if we don't already have one32    // This is very important, so we don't re-make a new client if React33    // suspends during the initial render. This may not be needed if we34    // have a suspense boundary BELOW the creation of the query client35    if (!browserQueryClient) browserQueryClient = makeQueryClient()36    return browserQueryClient37  }38}39 40export function Providers(props: { children: React.ReactNode }) {41  // NOTE: Avoid useState when initializing the query client if you don't42  //       have a suspense boundary between this and the code that may43  //       suspend because React will throw away the client on the initial44  //       render if it suspends and there is no boundary45  const queryClient = getQueryClient()46 47  return (48    <QueryClientProvider client={queryClient}>49      <ReactQueryStreamedHydration>50        {props.children}51      </ReactQueryStreamedHydration>52    </QueryClientProvider>53  )54}

更多信息,请参阅 Next.js Suspense 流式传输示例。

其最大优势在于:你不再需要手动预取查询即可让 SSR 正常工作,且结果仍可流式传输!这带来极佳的开发者体验(DX)与更低的代码复杂度。

其劣势最易通过回顾 性能与请求瀑布指南中的复杂请求瀑布示例 来理解。使用预取的服务端组件,可有效消除初始页面加载 及 后续导航中的请求瀑布。而这种无预取方案,仅能在初始页面加载时展平瀑布,但在页面导航时却退化为与原始示例相同的深层瀑布:

Code
11. |> JS for <Feed>22.   |> getFeed()33.     |> JS for <GraphFeedItem>44.       |> getGraphDataById()

这甚至比 getServerSideProps/getStaticProps 更差,因为后者至少还能并行拉取数据与代码。

若你更看重开发者体验/迭代速度/上线速度与低代码复杂度,而非极致性能;或你的查询嵌套不深;又或你已通过 useSuspenseQueries 等工具掌握并行拉取以管控请求瀑布,则此权衡可能颇为合适。

或许可将两种方案结合使用,但我们尚未尝试。若你尝试了,请反馈结果,甚至可更新本文档并分享技巧!

最后的话

服务端组件与流式传输仍是相对较新的概念,我们仍在探索 React Query 如何融入其中,以及如何改进 API。我们欢迎建议、反馈与缺陷报告!

同样,要在一份指南中首次即完整涵盖这一新范式的全部细节,实属不可能。若你发现此处缺少某些信息,或对如何改进内容有建议,也请联系我们;更欢迎点击下方“在 GitHub 上编辑”按钮,助我们完善文档。

延伸阅读

要判断你的应用在使用服务端组件的同时能否从 React Query 中获益,请参阅文章 你或许并不需要 React Query。

本页为 TanStack Query 文档(React 版)的非官方中文翻译,原文由上游团队维护,遵循 MIT 许可。 译文如有偏差,以英文原文为准。

译自上游 59d11de