框架
版本

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

Codemod

为了使导入迁移更容易,v4 附带了一个 codemod。

codemod 是尽力帮助您迁移重大更改的尝试。请彻底审查生成的代码!此外,还有一些边缘情况无法通过代码修改找到,因此请密切关注日志输出。

您可以使用以下命令之一(或两者都用)轻松应用它

如果您想针对 .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 作为解析器;否则,codemod 将无法正确应用!

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

注意: codemod 将更改导入 - 您仍然必须手动安装单独的 devtools 包。

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

在 v3 中,Query 和 Mutation Keys 可以是 String 或 Array。在内部,React Query 始终仅使用 Array Keys 工作,并且我们有时会向消费者公开这一点。例如,在 queryFn 中,您将始终获得作为 Array 的 key,以便更轻松地使用 默认 Query 函数

但是,我们没有将这个概念贯彻到所有 api 中。例如,当在 Query 过滤器 上使用 predicate 函数时,您将获得原始 Query Key。如果您使用混合了 Arrays 和 Strings 的 Query Keys,这将使使用此类函数变得困难。使用全局回调时也是如此。

为了简化所有 api,我们决定将所有 key 都设为仅限 Arrays

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

Codemod

为了使迁移更容易,我们决定交付一个 codemod。

codemod 是尽力帮助您迁移重大更改的尝试。请彻底审查生成的代码!此外,还有一些边缘情况无法通过代码修改找到,因此请密切关注日志输出。

您可以使用以下命令之一(或两者都用)轻松应用它

如果您想针对 .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 作为解析器;否则,codemod 将无法正确应用!

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

已移除 idle 状态

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

这主要会影响 disabled 且没有任何 data 的 queries,因为这些 queries 之前处于 idle 状态

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

另请参阅 关于依赖 queries 的指南

禁用的 queries

由于此更改,禁用的 queries(即使是临时禁用的 queries)将以 loading 状态启动。为了使迁移更容易,特别是为了获得一个良好的标志来知道何时显示加载指示器,您可以检查 isInitialLoading 而不是 isLoading

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

另请参阅关于 禁用 queries 的指南

useQueries 的新 API

useQueries hook 现在接受一个带有 queries prop 的对象作为其输入。queries prop 的值是一个 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 是成功 queries 的非法缓存值

为了使通过返回 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,该 error 也将在开发模式下记录到控制台。

默认情况下,Queries 和 mutations 需要网络连接才能运行

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

即使 React Query 是一个异步状态管理器,可以用于任何产生 Promise 的事物,但它最常用于数据获取,并结合数据获取库。这就是为什么,默认情况下,如果没有任何网络连接,queries 和 mutations 将被 paused。如果您想选择加入以前的行为,您可以为 queries 和 mutations 全局设置 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" 的 queries 都应通过删除此选项来更新。

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

notifyOnChangePropsExclusion 已被移除

在 v4 中,notifyOnChangeProps 默认为 v3 的 "tracked" 行为,而不是 undefined。既然 "tracked" 是 v4 的默认行为,那么包含此配置选项就没有任何意义了。

cancelRefetch 的一致行为

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

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

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

我们认为,如果 query 被您编写的某些代码主动重新获取,则默认情况下,它应该重新启动获取。

这就是为什么此标志现在对于上面提到的所有方法都默认为 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 })

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

Query 过滤器

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

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 可能会匹配所有 queries,或者不匹配任何 queries,这没有多大意义。

在 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 queries。

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

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

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

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

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

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

插件 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 ++]

不再支持 promises 上的 cancel 方法

旧的 cancel 方法 允许您在 promises 上定义一个 cancel 函数,该函数然后被库用于支持 query 取消,已被移除。我们建议使用 更新的 API(在 v3.30.0 中引入),该 API 在内部使用 AbortController API,并为您提供一个 AbortSignal 实例,供您的 query 函数支持 query 取消。

TypeScript

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

支持的浏览器

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

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 会将 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 ++]

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 总是会触发查询和 mutation,但随后假设如果你想重试,你需要连接到互联网。 这导致了一些令人困惑的情况

  • 你处于离线状态并挂载一个查询 - 它进入加载状态,请求失败,并且它一直保持在加载状态直到你再次上线,即使它实际上并没有在获取数据。
  • 类似地,如果你处于离线状态并且关闭了重试,你的查询将只会触发并失败,并且查询进入错误状态。
  • 你处于离线状态并且想要触发一个不一定需要网络连接的查询 (因为你可以将 React Query 用于数据获取以外的其他用途),但由于某些其他原因它失败了。 该查询现在将被暂停,直到你再次上线。
  • 如果你处于离线状态,窗口焦点重新获取 (Window focus refetching) 根本不会做任何事情。

在 v4 中,React Query 引入了一个新的 networkMode 来解决所有这些问题。 请阅读关于新的 Network mode 的专门页面以获取更多信息。

默认情况下跟踪 Queries

React Query 默认使用 "跟踪" 查询属性,这应该会给你带来渲染优化的良好提升。 该功能自 v3.6.0 版本就已存在,现在已成为 v4 的默认行为。

使用 setQueryData 退出更新

当使用 setQueryData 的函数式更新器形式时,你现在可以通过返回 undefined 来退出更新。 如果 undefined 作为 previousValue 提供给你,这将很有帮助,这意味着当前不存在缓存条目,并且你不想/不能创建一个,就像在切换 todo 的示例中一样。

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

Mutation 缓存垃圾回收

现在 mutations 也可以像 queries 一样自动进行垃圾回收。 mutations 的默认 cacheTime 也设置为 5 分钟。

用于多个 Providers 的自定义 Contexts

现在可以指定自定义上下文 (Custom contexts) 以将 hooks 与其匹配的 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
  ...
}