框架
版本

迁移到 React Query 4

重大更改

v4 是一个主版本,因此有一些重大更改需要注意

react-query 现在是 @tanstack/react-query

您需要卸载/安装依赖项并更改导入

npm uninstall react-query
npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
npm uninstall react-query
npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
tsx
- import { useQuery } from 'react-query' // [!code --]
- import { ReactQueryDevtools } from 'react-query/devtools' // [!code --]

+ import { useQuery } from '@tanstack/react-query' // [!code ++]
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools' // [!code ++]
- import { useQuery } from 'react-query' // [!code --]
- import { ReactQueryDevtools } from 'react-query/devtools' // [!code --]

+ import { useQuery } from '@tanstack/react-query' // [!code ++]
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools' // [!code ++]

代码转换工具

为了简化导入迁移,v4 提供了一个代码转换工具。

该代码转换工具是尽最大努力帮助您迁移重大更改的尝试。请仔细审查生成的代码!此外,代码转换工具无法找到一些边缘情况,因此请留意日志输出。

您可以通过使用以下一个或两个命令轻松应用它

如果您想针对 .js.jsx 文件运行它,请使用以下命令

npx jscodeshift ./path/to/src/ \
  --extensions=js,jsx \
  --transform=./node_modules/@tanstack/react-query/codemods/v4/replace-import-specifier.js
npx jscodeshift ./path/to/src/ \
  --extensions=js,jsx \
  --transform=./node_modules/@tanstack/react-query/codemods/v4/replace-import-specifier.js

如果您想针对 .ts.tsx 文件运行它,请使用以下命令

npx jscodeshift ./path/to/src/ \
  --extensions=ts,tsx \
  --parser=tsx \
  --transform=./node_modules/@tanstack/react-query/codemods/v4/replace-import-specifier.js
npx jscodeshift ./path/to/src/ \
  --extensions=ts,tsx \
  --parser=tsx \
  --transform=./node_modules/@tanstack/react-query/codemods/v4/replace-import-specifier.js

请注意,对于 TypeScript,您需要使用 tsx 作为解析器;否则,代码转换工具将无法正确应用!

注意: 应用代码转换工具可能会破坏您的代码格式,因此在应用代码转换工具后,请不要忘记运行 prettier 和/或 eslint

注意: 代码转换工具会更改导入 - 您仍然需要手动安装单独的 devtools 包。

Query Keys (和 Mutation Keys) 需要是数组

在 v3 中,Query 和 Mutation Keys 可以是字符串或数组。在内部,React Query 一直只使用数组键,并且我们有时会将其暴露给消费者。例如,在 queryFn 中,您总是会收到一个数组形式的键,以便更容易地使用 默认查询函数

但是,我们并未将此概念应用到所有 API。例如,在使用 predicate 函数进行 查询过滤 时,您会收到原始的 Query Key。如果您使用的 Query Keys 是混合数组和字符串,这将使得处理这些函数变得困难。在使用全局回调时也是如此。

为了统一所有 API,我们决定只允许使用数组作为键

tsx
;-useQuery('todos', fetchTodos) + // [!code --]
  useQuery(['todos'], fetchTodos) // [!code ++]
;-useQuery('todos', fetchTodos) + // [!code --]
  useQuery(['todos'], fetchTodos) // [!code ++]

代码转换工具

为了简化这次迁移,我们决定提供一个代码转换工具。

该代码转换工具是尽最大努力帮助您迁移重大更改的尝试。请仔细审查生成的代码!此外,代码转换工具无法找到一些边缘情况,因此请留意日志输出。

您可以通过使用以下一个或两个命令轻松应用它

如果您想针对 .js.jsx 文件运行它,请使用以下命令

npx jscodeshift ./path/to/src/ \
  --extensions=js,jsx \
  --transform=./node_modules/@tanstack/react-query/codemods/v4/key-transformation.js
npx jscodeshift ./path/to/src/ \
  --extensions=js,jsx \
  --transform=./node_modules/@tanstack/react-query/codemods/v4/key-transformation.js

如果您想针对 .ts.tsx 文件运行它,请使用以下命令

npx jscodeshift ./path/to/src/ \
  --extensions=ts,tsx \
  --parser=tsx \
  --transform=./node_modules/@tanstack/react-query/codemods/v4/key-transformation.js
npx jscodeshift ./path/to/src/ \
  --extensions=ts,tsx \
  --parser=tsx \
  --transform=./node_modules/@tanstack/react-query/codemods/v4/key-transformation.js

请注意,对于 TypeScript,您需要使用 tsx 作为解析器;否则,代码转换工具将无法正确应用!

注意: 应用代码转换工具可能会破坏您的代码格式,因此在应用代码转换工具后,请不要忘记运行 prettier 和/或 eslint

已移除 idle 状态

随着新的 fetchStatus 的引入,以提供更好的离线支持,idle 状态变得无关紧要,因为 fetchStatus: 'idle' 更好地捕获了相同的状态。有关更多信息,请阅读 为什么需要两种不同的状态

这主要会影响那些还没有 datadisabled 查询,因为它们以前处于 idle 状态

tsx
- status: 'idle' // [!code --]
+ status: 'loading'  // [!code ++]
+ fetchStatus: 'idle' // [!code ++]
- status: 'idle' // [!code --]
+ status: 'loading'  // [!code ++]
+ fetchStatus: 'idle' // [!code ++]

另外,请查看 关于依赖查询的指南

禁用查询 (disabled queries)

由于此更改,禁用的查询(即使是暂时禁用的)将从 loading 状态开始。为了简化迁移,特别是为了有一个良好的标志来知道何时显示加载微调器,您可以检查 isInitialLoading 而不是 isLoading

tsx
;-isLoading + // [!code --]
  isInitialLoading // [!code ++]
;-isLoading + // [!code --]
  isInitialLoading // [!code ++]

另请参阅关于 禁用查询 的指南

useQueries 的新 API

useQueries hook 现在接受一个带有 queries prop 的对象作为输入。queries prop 的值是一个查询数组(该数组与 v3 中传递给 useQueries 的内容相同)。

tsx
;-useQueries([
  { queryKey1, queryFn1, options1 },
  { queryKey2, queryFn2, options2 },
]) + // [!code --]
  useQueries({
    queries: [
      { queryKey1, queryFn1, options1 },
      { queryKey2, queryFn2, options2 },
    ],
  }) // [!code ++]
;-useQueries([
  { queryKey1, queryFn1, options1 },
  { queryKey2, queryFn2, options2 },
]) + // [!code --]
  useQueries({
    queries: [
      { queryKey1, queryFn1, options1 },
      { queryKey2, queryFn2, options2 },
    ],
  }) // [!code ++]

对于成功的查询,undefined 是不允许的缓存值

为了通过返回 undefined 来实现避免更新,我们将 undefined 设为不允许的缓存值。这与其他 react-query 的概念一致,例如,从 initialData 函数返回 undefined不会设置数据。

此外,通过在 queryFn 中添加日志记录,很容易产生 Promise<void> 的错误

tsx
useQuery(['key'], () =>
  axios.get(url).then((result) => console.log(result.data)),
)
useQuery(['key'], () =>
  axios.get(url).then((result) => console.log(result.data)),
)

这在类型级别上是不允许的;在运行时,undefined 将被转换为一个失败的 Promise,这意味着您将得到一个 error,在开发模式下也会将其记录到控制台。

查询和突变默认需要网络连接才能运行

请阅读关于在线/离线支持的 新功能公告,以及关于 网络模式 的专用页面

尽管 React Query 是一个异步状态管理器,可用于任何产生 Promise 的内容,但它最常用于与数据获取库结合的数据获取。因此,默认情况下,如果网络连接不可用,查询和突变将进入paused 状态。如果您想选择以前的行为,可以在全局范围内为查询和突变设置 networkMode: offlineFirst

tsx
new QueryClient({
  defaultOptions: {
    queries: {
      networkMode: 'offlineFirst',
    },
    mutations: {
      networkMode: 'offlineFirst',
    },
  },
})
new QueryClient({
  defaultOptions: {
    queries: {
      networkMode: 'offlineFirst',
    },
    mutations: {
      networkMode: 'offlineFirst',
    },
  },
})

notifyOnChangeProps 属性不再接受 "tracked" 作为值

notifyOnChangeProps 选项不再接受 "tracked" 值。相反,useQuery 默认会跟踪属性。所有使用 notifyOnChangeProps: "tracked" 的查询都应通过移除此选项进行更新。

如果您想在任何查询中绕过此设置以模仿 v3 中每次查询更改时重新渲染的默认行为,notifyOnChangeProps 现在接受 "all" 值,以选择退出默认的智能跟踪优化。

已移除 notifyOnChangePropsExclusion

在 v4 中,notifyOnChangeProps 默认使用 v3 的 "tracked" 行为,而不是 undefined。既然 "tracked" 是 v4 的默认行为,因此不再需要包含此配置选项。

cancelRefetch 的一致行为

cancelRefetch 选项可以传递给所有以命令式方式获取查询的函数,即

  • queryClient.refetchQueries
  • queryClient.invalidateQueries
  • queryClient.resetQueries
  • useQuery 返回的 refetch
  • useInfiniteQuery 返回的 fetchNextPagefetchPreviousPage

除了 fetchNextPagefetchPreviousPage 之外,此标志默认设置为 false,这不一致且可能带来麻烦:在突变后调用 refetchQueriesinvalidateQueries 可能不会产生最新结果,因为先前的缓慢获取已在进行中,并且此重新获取将被跳过。

我们认为,如果您通过代码主动重新获取查询,它应该默认重新启动获取。

因此,此标志现在对上述所有方法都默认设置为true。这也意味着,如果您连续两次调用 refetchQueries 而不等待它,它将取消第一次获取并用第二次重新启动它

queryClient.refetchQueries({ queryKey: ['todos'] })
// this will abort the previous refetch and start a new fetch
queryClient.refetchQueries({ queryKey: ['todos'] })
queryClient.refetchQueries({ queryKey: ['todos'] })
// this will abort the previous refetch and start a new fetch
queryClient.refetchQueries({ queryKey: ['todos'] })

您可以显式传递 cancelRefetch:false 来选择退出此行为

queryClient.refetchQueries({ queryKey: ['todos'] })
// this will not abort the previous refetch - it will just be ignored
queryClient.refetchQueries({ queryKey: ['todos'] }, { cancelRefetch: false })
queryClient.refetchQueries({ queryKey: ['todos'] })
// this will not abort the previous refetch - it will just be ignored
queryClient.refetchQueries({ queryKey: ['todos'] }, { cancelRefetch: false })

注意:对于自动触发的获取,例如因为查询挂载或因为窗口焦点重新获取,行为没有改变。

查询过滤器

一个 查询过滤器 是一个带有匹配查询条件的对象的。历史上,过滤器选项大部分是布尔标志的组合。然而,组合这些标志可能导致不可能的状态。具体来说

active?: boolean
  - When set to true it will match active queries.
  - When set to false it will match inactive queries.
inactive?: boolean
  - When set to true it will match inactive queries.
  - When set to false it will match active queries.
active?: boolean
  - When set to true it will match active queries.
  - When set to false it will match inactive queries.
inactive?: boolean
  - When set to true it will match inactive queries.
  - When set to false it will match active queries.

当这些标志一起使用时,它们效果不佳,因为它们是互斥的。根据描述,将两个标志都设置为 false 可能会匹配所有查询,或者不匹配任何查询,这没有多大意义。

在 v4 中,这些过滤器已合并为单个过滤器,以更好地显示意图

tsx
- active?: boolean // [!code --]
- inactive?: boolean // [!code --]
+ type?: 'active' | 'inactive' | 'all' // [!code ++]
- active?: boolean // [!code --]
- inactive?: boolean // [!code --]
+ type?: 'active' | 'inactive' | 'all' // [!code ++]

过滤器默认为 all,您可以选择只匹配 activeinactive 查询。

refetchActive / refetchInactive

queryClient.invalidateQueries 还有两个额外的、类似的标志

refetchActive: Boolean
  - Defaults to true
  - When set to false, queries that match the refetch predicate and are actively being rendered
    via useQuery and friends will NOT be refetched in the background, and only marked as invalid.
refetchInactive: Boolean
  - Defaults to false
  - When set to true, queries that match the refetch predicate and are not being rendered
    via useQuery and friends will be both marked as invalid and also refetched in the background
refetchActive: Boolean
  - Defaults to true
  - When set to false, queries that match the refetch predicate and are actively being rendered
    via useQuery and friends will NOT be refetched in the background, and only marked as invalid.
refetchInactive: Boolean
  - Defaults to false
  - When set to true, queries that match the refetch predicate and are not being rendered
    via useQuery and friends will be both marked as invalid and also refetched in the background

出于相同的原因,这些也已合并

tsx
- refetchActive?: boolean // [!code --]
- refetchInactive?: boolean // [!code --]
+ refetchType?: 'active' | 'inactive' | 'all' | 'none' // [!code ++]
- refetchActive?: boolean // [!code --]
- refetchInactive?: boolean // [!code --]
+ refetchType?: 'active' | 'inactive' | 'all' | 'none' // [!code ++]

此标志默认为 active,因为 refetchActive 默认为 true。这意味着我们还需要一种方法来告知 invalidateQueries 完全不重新获取,这就是为什么这里也允许第四个选项(none)。

onSuccess 不再从 setQueryData 调用

这让许多人感到困惑,并且当 setQueryDataonSuccess 中调用时,还会导致无限循环。当与 staleTime 结合使用时,这也是一个常见的错误源,因为如果数据仅从缓存读取,则不会调用 onSuccess

onErroronSettled 类似,onSuccess 回调现在与请求的发出相关联。没有请求 -> 没有回调。

如果您想监听 data 字段的变化,最好使用 useEffect,其中 data 是依赖数组的一部分。由于 React Query 通过结构共享确保数据稳定,因此效果不会在每次后台重新获取时执行,而仅在数据中的某些内容发生更改时执行

const { data } = useQuery({ queryKey, queryFn })
React.useEffect(() => mySideEffectHere(data), [data])
const { data } = useQuery({ queryKey, queryFn })
React.useEffect(() => mySideEffectHere(data), [data])

persistQueryClient 和相应的 persister 插件不再是实验性的,并且已重命名

插件 createWebStoragePersistorcreateAsyncStoragePersistor 已分别重命名为 createSyncStoragePersistercreateAsyncStoragePersistorpersistQueryClient 中的接口 Persistor 也已重命名为 Persister。请查看 此 stackexchange 以了解更改的动机。

由于这些插件不再是实验性的,它们的导入路径也已更新

tsx
- import { persistQueryClient } from 'react-query/persistQueryClient-experimental' // [!code --]
- import { createWebStoragePersistor } from 'react-query/createWebStoragePersistor-experimental' // [!code --]
- import { createAsyncStoragePersistor } from 'react-query/createAsyncStoragePersistor-experimental' // [!code --]

+ import { persistQueryClient } from '@tanstack/react-query-persist-client' // [!code ++]
+ import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister' // [!code ++]
+ import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'  // [!code ++]
- import { persistQueryClient } from 'react-query/persistQueryClient-experimental' // [!code --]
- import { createWebStoragePersistor } from 'react-query/createWebStoragePersistor-experimental' // [!code --]
- import { createAsyncStoragePersistor } from 'react-query/createAsyncStoragePersistor-experimental' // [!code --]

+ import { persistQueryClient } from '@tanstack/react-query-persist-client' // [!code ++]
+ import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister' // [!code ++]
+ import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'  // [!code ++]

Promises 上的 cancel 方法不再受支持

允许您在 Promises 上定义 cancel 函数的 cancel 方法,然后由库用于支持查询取消,已被移除。我们建议使用(v3.30.0 引入的)新 API 来进行查询取消,该 API 内部使用 AbortController API,并为您提供一个 AbortSignal 实例供您的 queryFn 使用,以支持查询取消。

TypeScript

类型现在要求使用 TypeScript v4.1 或更高版本

支持的浏览器

从 v4 开始,React Query 针对现代浏览器进行了优化。我们更新了 browserslist 以生成更现代、性能更好、体积更小的包。您可以在 这里 阅读有关要求的信息。

已移除 setLogger

通过调用 setLogger 可以全局更改 logger。在 v4 中,该函数被创建 QueryClient 时的可选字段所取代。

tsx
- import { QueryClient, setLogger } from 'react-query'; // [!code --]
+ import { QueryClient } from '@tanstack/react-query'; // [!code ++]

- setLogger(customLogger) // [!code --]
- const queryClient = new QueryClient(); // [!code --]
+ const queryClient = new QueryClient({ logger: customLogger }) // [!code ++]
- import { QueryClient, setLogger } from 'react-query'; // [!code --]
+ import { QueryClient } from '@tanstack/react-query'; // [!code ++]

- setLogger(customLogger) // [!code --]
- const queryClient = new QueryClient(); // [!code --]
+ const queryClient = new QueryClient({ logger: customLogger }) // [!code ++]

服务端没有默认的手动垃圾回收

在 v3 中,React Query 会默认缓存查询结果 5 分钟,然后手动进行垃圾回收。此默认值也适用于服务器端 React Query。

这导致内存消耗过高以及进程挂起等待此手动垃圾回收完成。在 v4 中,默认情况下,服务器端 cacheTime 现在设置为 Infinity,实际上禁用了手动垃圾回收(NodeJS 进程将在请求完成后清除所有内容)。

此更改仅影响服务器端 React Query 的用户,例如与 Next.js 一起使用。如果您手动设置了 cacheTime,这不会影响您(尽管您可能希望镜像行为)。

生产环境中的日志记录

从 v4 开始,React Query 在生产模式下不再将错误(例如,失败的获取)记录到控制台,因为这让许多人感到困惑。错误仍然会在开发模式下显示。

ESM 支持

React Query 现在支持 package.json "exports",并且完全兼容 Node 对 CommonJS 和 ESM 的原生解析。我们预计这对大多数用户来说都不是重大更改,但这会将您可以在项目中导入的文件限制为我们官方支持的入口点。

简化的 NotifyEvents

手动订阅 QueryCache 始终会为您提供一个 QueryCacheNotifyEvent,但 MutationCache 则不是。我们已简化了行为,并相应地调整了事件名称。

QueryCacheNotifyEvent

tsx
- type: 'queryAdded' // [!code --]
+ type: 'added' // [!code ++]
- type: 'queryRemoved' // [!code --]
+ type: 'removed' // [!code ++]
- type: 'queryUpdated' // [!code --]
+ type: 'updated' // [!code ++]
- type: 'queryAdded' // [!code --]
+ type: 'added' // [!code ++]
- type: 'queryRemoved' // [!code --]
+ type: 'removed' // [!code ++]
- type: 'queryUpdated' // [!code --]
+ type: 'updated' // [!code ++]

MutationCacheNotifyEvent

MutationCacheNotifyEvent 使用与 QueryCacheNotifyEvent 相同的类型。

注意:这仅与通过 queryCache.subscribemutationCache.subscribe 手动订阅缓存的用户相关

已移除单独的 hydration 导出

随着版本 3.22.0,hydration 工具已移至 React Query 核心。在 v3 中,您仍然可以使用来自 react-query/hydration 的旧导出,但这些导出在 v4 中已被移除。

tsx
- import { dehydrate, hydrate, useHydrate, Hydrate } from 'react-query/hydration' // [!code --]
+ import { dehydrate, hydrate, useHydrate, Hydrate } from '@tanstack/react-query' // [!code ++]
- import { dehydrate, hydrate, useHydrate, Hydrate } from 'react-query/hydration' // [!code --]
+ import { dehydrate, hydrate, useHydrate, Hydrate } from '@tanstack/react-query' // [!code ++]

queryClient, querymutation 中移除了未文档化的方法

QueryClient 上的 cancelMutationsexecuteMutation 方法未进行文档化且在内部未使用,因此我们将其移除。由于它只是 mutationCache 上可用方法的一个包装器,您仍然可以使用 executeMutation 的功能

tsx
- executeMutation< // [!code --]
-   TData = unknown, // [!code --]
-   TError = unknown, // [!code --]
-   TVariables = void, // [!code --]
-   TContext = unknown // [!code --]
- >( // [!code --]
-   options: MutationOptions<TData, TError, TVariables, TContext> // [!code --]
- ): Promise<TData> { // [!code --]
-   return this.mutationCache.build(this, options).execute() // [!code --]
- } // [!code --]
- executeMutation< // [!code --]
-   TData = unknown, // [!code --]
-   TError = unknown, // [!code --]
-   TVariables = void, // [!code --]
-   TContext = unknown // [!code --]
- >( // [!code --]
-   options: MutationOptions<TData, TError, TVariables, TContext> // [!code --]
- ): Promise<TData> { // [!code --]
-   return this.mutationCache.build(this, options).execute() // [!code --]
- } // [!code --]

此外,query.setDefaultOptions 已被移除,因为它也没有被使用。mutation.cancel 已被移除,因为它实际上并未取消正在进行的请求。

src/react 目录已重命名为 src/reactjs

以前,React Query 有一个名为 react 的目录,它从 react 模块导入。这可能会导致某些 Jest 配置出现问题,从而在运行测试时出现错误,例如

TypeError: Cannot read property 'createContext' of undefined
TypeError: Cannot read property 'createContext' of undefined

随着目录的重命名,这个问题不再存在。

如果您直接在项目中从 'react-query/react' 导入任何内容(而不是仅从 'react-query' 导入),那么您需要更新您的导入

tsx
- import { QueryClientProvider } from 'react-query/react'; // [!code --]
+ import { QueryClientProvider } from '@tanstack/react-query/reactjs'; // [!code ++]
- import { QueryClientProvider } from 'react-query/react'; // [!code --]
+ import { QueryClientProvider } from '@tanstack/react-query/reactjs'; // [!code ++]

新功能 🚀

v4 带来了一系列很棒的新功能

支持 React 18

React 18 于今年早些时候发布,v4 现在对其及其带来的新并发功能提供了一级支持。

完善的离线支持

在 v3 中,React Query 总是会触发查询和突变,但然后假定如果您想重试,您需要连接到互联网。这导致了许多令人困惑的情况

  • 您处于离线状态并挂载了一个查询 - 它进入加载状态,请求失败,并且在您重新上线之前一直处于加载状态,即使它实际上并未在获取。
  • 同样,如果您处于离线状态并且重试次数已关闭,您的查询将直接发出并失败,然后查询进入错误状态。
  • 您处于离线状态,并希望触发一个不一定需要网络连接的查询(因为您可以将 React Query 用于数据获取以外的其他用途),但它由于其他原因失败了。该查询现在将暂停,直到您重新上线。
  • 窗口焦点重新获取在离线时根本无效。

在 v4 中,React Query 引入了新的 networkMode 来解决所有这些问题。请阅读关于新 网络模式 的专用页面以获取更多信息。

默认跟踪查询 (Tracked Queries per default)

React Query 默认“跟踪”查询属性,这应该能为您带来显著的渲染优化。该功能自 v3.6.0 以来一直存在,现在在 v4 中已成为默认行为。

通过 setQueryData 避免更新

在使用 setQueryData 的函数式更新器形式 时,您现在可以通过返回 undefined 来避免更新。如果 undefined 作为 previousValue 提供给您,这会很有帮助,这意味着当前没有缓存条目存在,并且您不想/无法创建它,例如在切换待办事项的示例中

tsx
queryClient.setQueryData(['todo', id], (previousTodo) =>
  previousTodo ? { ...previousTodo, done: true } : undefined,
)
queryClient.setQueryData(['todo', id], (previousTodo) =>
  previousTodo ? { ...previousTodo, done: true } : undefined,
)

Mutation Cache 垃圾回收

与查询一样,突变现在也可以自动进行垃圾回收。突变的默认 cacheTime 也设置为 5 分钟。

多 Provider 的自定义 Context

现在可以指定自定义上下文,以将 hook 与其匹配的 Provider 配对。当组件树中可能存在多个 React Query Provider 实例时,这一点至关重要,您需要确保您的 hook 使用正确的 Provider 实例。

一个例子

  1. 创建一个数据包。
tsx
// Our first data package: @my-scope/container-data

const context = React.createContext<QueryClient | undefined>(undefined)
const queryClient = new QueryClient()

export const useUser = () => {
  return useQuery(USER_KEY, USER_FETCHER, {
    context,
  })
}

export const ContainerDataProvider = ({
  children,
}: {
  children: React.ReactNode
}) => {
  return (
    <QueryClientProvider client={queryClient} context={context}>
      {children}
    </QueryClientProvider>
  )
}
// Our first data package: @my-scope/container-data

const context = React.createContext<QueryClient | undefined>(undefined)
const queryClient = new QueryClient()

export const useUser = () => {
  return useQuery(USER_KEY, USER_FETCHER, {
    context,
  })
}

export const ContainerDataProvider = ({
  children,
}: {
  children: React.ReactNode
}) => {
  return (
    <QueryClientProvider client={queryClient} context={context}>
      {children}
    </QueryClientProvider>
  )
}
  1. 创建第二个数据包。
tsx
// Our second data package: @my-scope/my-component-data

const context = React.createContext<QueryClient | undefined>(undefined)
const queryClient = new QueryClient()

export const useItems = () => {
  return useQuery(ITEMS_KEY, ITEMS_FETCHER, {
    context,
  })
}

export const MyComponentDataProvider = ({
  children,
}: {
  children: React.ReactNode
}) => {
  return (
    <QueryClientProvider client={queryClient} context={context}>
      {children}
    </QueryClientProvider>
  )
}
// Our second data package: @my-scope/my-component-data

const context = React.createContext<QueryClient | undefined>(undefined)
const queryClient = new QueryClient()

export const useItems = () => {
  return useQuery(ITEMS_KEY, ITEMS_FETCHER, {
    context,
  })
}

export const MyComponentDataProvider = ({
  children,
}: {
  children: React.ReactNode
}) => {
  return (
    <QueryClientProvider client={queryClient} context={context}>
      {children}
    </QueryClientProvider>
  )
}
  1. 在您的应用程序中使用这两个数据包。
tsx
// Our application

import { ContainerDataProvider, useUser } from "@my-scope/container-data";
import { AppDataProvider } from "@my-scope/app-data";
import { MyComponentDataProvider, useItems } from "@my-scope/my-component-data";

<ContainerDataProvider> // <-- Provides container data (like "user") using its own React Query provider
  ...
  <AppDataProvider> // <-- Provides app data using its own React Query provider (unused in this example)
    ...
      <MyComponentDataProvider> // <-- Provides component data (like "items") using its own React Query provider
        <MyComponent />
      </MyComponentDataProvider>
    ...
  </AppDataProvider>
  ...
</ContainerDataProvider>

// Example of hooks provided by the "DataProvider" components above:
const MyComponent = () => {
  const user = useUser() // <-- Uses the context specified in ContainerDataProvider.
  const items = useItems() // <-- Uses the context specified in MyComponentDataProvider
  ...
}
// Our application

import { ContainerDataProvider, useUser } from "@my-scope/container-data";
import { AppDataProvider } from "@my-scope/app-data";
import { MyComponentDataProvider, useItems } from "@my-scope/my-component-data";

<ContainerDataProvider> // <-- Provides container data (like "user") using its own React Query provider
  ...
  <AppDataProvider> // <-- Provides app data using its own React Query provider (unused in this example)
    ...
      <MyComponentDataProvider> // <-- Provides component data (like "items") using its own React Query provider
        <MyComponent />
      </MyComponentDataProvider>
    ...
  </AppDataProvider>
  ...
</ContainerDataProvider>

// Example of hooks provided by the "DataProvider" components above:
const MyComponent = () => {
  const user = useUser() // <-- Uses the context specified in ContainerDataProvider.
  const items = useItems() // <-- Uses the context specified in MyComponentDataProvider
  ...
}