框架
版本

迁移到 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

**注意:**代码转换工具_仅_更改导入 - 您仍然需要手动安装单独的开发者工具包。

查询键(和变更键)必须是数组

在 v3 中,查询和变更键可以是字符串或数组。在内部,React Query 始终仅使用数组键,并且我们有时会将此公开给使用者。例如,在 queryFn 中,您始终会以数组形式获取键,以便更轻松地使用默认查询函数

但是,我们并未将此概念贯彻到所有 API 中。例如,当在查询过滤器上使用 predicate 函数时,您将获得原始查询键。如果您使用混合数组和字符串的查询键,这将使使用此类函数变得困难。全局回调也是如此。

为了简化所有 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

空闲状态已被删除

随着新的 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 ++]

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

禁用查询

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

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

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

useQueries 的新 API

useQueries 钩子现在接受一个包含 queries 属性的对象作为其输入。queries 属性的值是一个查询数组(此数组与 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'] })
// 这将中止先前的重新获取并开始新的获取
queryClient.refetchQueries({ queryKey: ['todos'] })
queryClient.refetchQueries({ queryKey: ['todos'] })
// 这将中止先前的重新获取并开始新的获取
queryClient.refetchQueries({ queryKey: ['todos'] })

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

queryClient.refetchQueries({ queryKey: ['todos'] })
// 这不会中止先前的重新获取 - 它只会被忽略
queryClient.refetchQueries({ queryKey: ['todos'] }, { cancelRefetch: false })
queryClient.refetchQueries({ queryKey: ['todos'] })
// 这不会中止先前的重新获取 - 它只会被忽略
queryClient.refetchQueries({ queryKey: ['todos'] }, { cancelRefetch: false })

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

查询过滤器

查询过滤器是一个具有某些条件的对象,用于匹配查询。从历史上看,过滤器选项大多是布尔标志的组合。但是,组合这些标志可能会导致不可能的状态。具体来说:

active?: boolean
  - 设置为 true 时,它将匹配活动查询。
  - 设置为 false 时,它将匹配非活动查询。
inactive?: boolean
  - 设置为 true 时,它将匹配非活动查询。
  - 设置为 false 时,它将匹配活动查询。
active?: boolean
  - 设置为 true 时,它将匹配活动查询。
  - 设置为 false 时,它将匹配非活动查询。
inactive?: boolean
  - 设置为 true 时,它将匹配非活动查询。
  - 设置为 false 时,它将匹配活动查询。

这些标志一起使用时效果不佳,因为它们是互斥的。从描述来看,将两个标志都设置为 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
  - 默认为 true
  - 设置为 false 时,与重新获取谓词匹配且通过 useQuery 及其相关项主动呈现的查询
    将不会在后台重新获取,而只会被标记为无效。
refetchInactive: Boolean
  - 默认为 false
  - 设置为 true 时,与重新获取谓词匹配且未通过 useQuery 及其相关项呈现的查询
    将被标记为无效并在后台重新获取
refetchActive: Boolean
  - 默认为 true
  - 设置为 false 时,与重新获取谓词匹配且通过 useQuery 及其相关项主动呈现的查询
    将不会在后台重新获取,而只会被标记为无效。
refetchInactive: Boolean
  - 默认为 false
  - 设置为 true 时,与重新获取谓词匹配且未通过 useQuery 及其相关项呈现的查询
    将被标记为无效并在后台重新获取

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

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 调用

这让许多人感到困惑,并且如果在 onSuccess 内部调用 setQueryData,还会产生无限循环。当与 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 和相应的持久化程序插件不再是实验性的,并且已重命名

插件 createWebStoragePersistorcreateAsyncStoragePersistor 已分别重命名为 createSyncStoragePersistercreateAsyncStoragePersisterpersistQueryClient 中的接口 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 ++]

Promise 上的 cancel 方法不再受支持

旧的 cancel 方法允许您在 Promise 上定义一个 cancel 函数,该函数随后被库用于支持查询取消,现已删除。我们建议使用较新的 API(从 v3.30.0 开始引入)进行查询取消,该 API 在内部使用 AbortController API 并为您提供一个 AbortSignal 实例 以供您的查询函数支持查询取消。

TypeScript

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

支持的浏览器

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

setLogger 已删除

以前可以通过调用 setLogger 来全局更改记录器��在 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 手动订阅缓存时才相关

单独的水合导出已被删除

从版本 3.22.0 开始,水合工具已移至 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 ++]

queryClientquerymutation 中删除了未记录的方法

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 来解决所有这些问题。有关新的网络模式的更多信息,请阅读专门的���面。

默认跟踪查询

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

变更缓存垃圾回收

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

多个提供程序的自定义上下文

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

一个例子:

  1. 创建一个数据包。
tsx
// 我们的第一个数据包:@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>
  )
}
// 我们的第一个数据包:@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
// 我们的第二个数据包:@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>
  )
}
// 我们的第二个数据包:@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
// 我们的应用程序

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> // <-- 使用其自己的 React Query 提供程序提供容器数据(例如“用户”)
  ...
  <AppDataProvider> // <-- 使用其自己的 React Query 提供程序提供应用程序数据(在此示例中未使用)
    ...
      <MyComponentDataProvider> // <-- 使用其自己的 React Query 提供程序提供组件数据(例如“项目”)
        <MyComponent />
      </MyComponentDataProvider>
    ...
  </AppDataProvider>
  ...
</ContainerDataProvider>

// 上面“DataProvider”组件提供的钩子示例:
const MyComponent = () => {
  const user = useUser() // <-- 使用 ContainerDataProvider 中指定的上下文。
  const items = useItems() // <-- 使用 MyComponentDataProvider 中指定的上下文
  ...
}
// 我们的应用程序

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> // <-- 使用其自己的 React Query 提供程序提供容器数据(例如“用户”)
  ...
  <AppDataProvider> // <-- 使用其自己的 React Query 提供程序提供应用程序数据(在此示例中未使用)
    ...
      <MyComponentDataProvider> // <-- 使用其自己的 React Query 提供程序提供组件数据(例如“项目”)
        <MyComponent />
      </MyComponentDataProvider>
    ...
  </AppDataProvider>
  ...
</ContainerDataProvider>

// 上面“DataProvider”组件提供的钩子示例:
const MyComponent = () => {
  const user = useUser() // <-- 使用 ContainerDataProvider 中指定的上下文。
  const items = useItems() // <-- 使用 MyComponentDataProvider 中指定的上下文
  ...
}