React Query 不是一个数据获取库,而是一个异步状态管理库
首先
这篇文章是基于Dominik先生撰写的《Thinking in React Query》总结的React Query的思维方式。Dominik先生是TanStack Query(以下简称为React Query)的核心维护者,我们可以从他的博客中获取关于React Query的见解。
Table of contents
- React Query不是一个数据获取库,而是一个用于管理异步状态(服务器状态)的库。它可以有效地利用缓存,并通过使用staleTime来控制缓存,以及适当地设置queryKey来提高性能。
? React Query不是一个数据获取库。
根据下图显示,在查看代码后很容易可以看出React Query并不是数据获取库。为什么呢?因为在queryFn中使用了axios。
这证明了React Query并不将数据获取视为其责任。
React Query不关心数据是如何获取的。它只关心queryFn返回的是一个Promise。因此,使用axios或fetch进行数据获取都是可以的。

如果您能理解React Query并不是一个数据获取库,那么您就不会产生以下这样的疑问。
-
- React QueryでどのようにbaseURLを設定するのか?
-
- React Queryでどのようにresponseのheaderにアクセスするのか?
- React QueryでどのようにgraphQLのrequestを作成するのか?
关于这些问题只有一个答案。
“这不是React Query所关心的事情。”
React Query是什么?
为了说明这一点,需要理解状态管理的概念,其中包括客户端状态、全局状态和服务器状态这三种状态。请参考关于React的状态策略的文章《使用“三种类型”进行状态管理的React策略》,对应将它们进行了以下分类。
-
- Client State
各Component内で管理される状態
Global State
ページをまたいで保持し続ける必要のある状態
Server State
サーバーデータのキャッシュ
在React Query和SWR等库出现之前,”Server State”这个概念并不普遍。换句话说,所有状态都需要通过”Client State”或”Global State”来管理,而服务器数据的管理是通过”Global State”来完成的。
这就是Redux。
在Redux中,无论是从服务器获取的数据还是应用程序内使用的UI状态都被同样处理。从这样的历史背景来看,就可以理解为什么需要像React Query这样的服务器缓存库。
客户端状态(全局状态)和服务器状态之间的种类是不同的。
由于Client State和Global State都是与客户端相关的状态,因此我们方便起见统称为Client State。
在Redux等框架中,所有状态都是由客户端状态(Client State)进行管理的。然而,随着服务器状态(Server State)的出现,出现了可以管理服务器状态的库,比如React Query。那么,为什么仅仅使用客户端状态管理会导致问题呢?原因在于客户端状态与服务器状态在状态的特征和角色上存在差异。我已经将它们的区别总结在下面的表格中。
客户端状态和服务器状态的区别
在中文中,以下是一个可能的翻译选项:
状态的所有权是由客户端或服务器拥有的。
首先,第一个区别是关于状态的所有者。对于客户端状态(例如UI状态等),客户端完全拥有状态。而用户信息、文章列表等数据则由服务器远程管理。
状态是否同步还是异步
客户端状态是指所有权完全在客户端的状态,因此可以同步处理。而服务器状态涉及数据的获取和更新,因此需要异步管理。
是否状态一直保持最新。
在中文中,可以这样表述:
最后,关键是要确保状态始终保持最新。客户端状态是不依赖外部的状态,因此可以保证始终是最新的,而服务器状态呢?当我们访问新闻网站时,不能保证信息在一小时后仍然是最新的。很可能会有新的文章更新或被删除。
换句话说,我们通过Client State管理的服务器状态仅仅是一个快照,不能保证始终是最新的状态。因此,我们有必要将责任分别放在Client State和Server State上,并确保适时地更新Server State。
考虑以上的内容,我们可以发现在处理Server State时需要意识到上述的三点。让我们在有意识的基础上思考React Query的作用。
React Query 是一个用于管理异步状态(服务器状态)的库。
重新考虑一下,我们回到这个结论。刚才,我主张Client State和Server State由于各自的特点和角色不同,应该将责任分开。那么,我们应该如何在异步方式下确保Server State的状态保持最新并加以管理呢?
在这里,React Query派上了用场!
例如,数据获取时的加载状态和错误处理。借助React Query,我们可以高效地管理这些异步状态。
为了理解这个优势,首先我们可以使用Redux Toolkit来实现加载状态的功能。
使用Redux Toolkit进行加载状态管理
const slice = createSlice({
name: "sample",
initialState: {
isLoading: false
},
// ⛑️ Promiseがpending・rejected・fulfilledでそれぞれisLoadingの状態を定義する
extraReducers: (builder) => {
builder.addCase(getIssue.pending, (state, action) => {
state.isLoading = true
})
builder.addCase(getIssue.rejected, (state, action) => {
state.isLoading = false
})
builder.addCase(getIssue.fulfilled, (state, action) => {
state.isLoading = false
})
},
})
根据Promise的不同状态(pending,rejected,fulfilled),它会更新isLoading。仅仅从这样的实现来看,状态管理似乎非常困难,但请想象一下,这样的处理对于每个终端点都是必需的。我们想要实现的只是在获取数据的过程中显示加载界面的需求。为此,Redux需要这样的实现。
让我们来看一下React Query中isLoading的状态管理方法。
使用React Query来管理加载状态
const { data, isLoading } = useQuery({
queryKey: ["issues"],
queryFn: () => axios.get("/issues").then(res => res.data)
})
和其他方式相比,有一目了然之处!React Query 将处理加载等状态的操作隐藏在内部,使我们不再需要实施异步服务器数据的状态管理。通过接收 Promise,React Query 能够高效地管理异步状态。我有以下图像化的想法。

React Query可以高效地管理异步状态(如加载等)
充分利用现金
通过React Query,我们能够高效地管理异步状态。然而,在处理服务器状态时,还有一个重要的点需要考虑。那就是我们需要尽可能地将状态保持最新。React Query的缓存机制为我们提供了在适当的时机更新状态的选项。
有关React Query缓存的事项。
React Query使用queryKey作为useQuery的第一个参数来管理缓存。缓存数据保存在JavaScript内存中,因此如果浏览器重新加载,缓存会被清除。现在,我们整理一下使用useQuery发送API请求并获取数据(包括是否有缓存)的流程。
-
- 在组件中调用useQuery,并开始数据获取
参考queryKey检查缓存
如果缓存命中,则将缓存数据返回给组件
如果未命中缓存,或者命中缓存但缓存处于过期状态,则执行步骤3
后台获取新数据并更新
将新数据保存到缓存并重新渲染
从这里可以看出,为了解决多次进行fetch处理的问题并实现高效的数据获取,可以利用缓存来获取相同的数据。
使用staleTime来控制缓存。
听到这种说法时,我不禁思考,如果我们延长缓存的存活时间并尽可能减少fetch处理,是否能提高性能呢?然而,这样做就等同于在Redux时代将服务器状态管理为快照一样,没有变化。
服务器状态的作用是管理异步状态并保持其最新状态。换句话说,我们需要正确设置缓存的存活时间。这就是staleTime所用的地方。
staleTime是将缓存置于stale(即被认为陈旧)状态的时间段。默认设置为0,如果将其设置为Infinity,则可以始终将缓存设置为fresh(即新鲜)状态。容易与staleTime混淆的是cacheTime。1cacheTime是指缓存在进行垃圾回收(即释放内存空间)之前的时间。默认设置为5分钟。
根据这些解释,可以考虑以下的缓存策略。
-
- 頻繁に更新されうるデータ
記事一覧など
staleTime: 0
常にキャッシュをstale状態にし、バックグラウンドでfetch処理を行い最新のデータを更新する
頻繁に更新されることがないデータ
設定内容など
staleTime: Infinity
キャッシュが常にfreshなのでネットワークのリクエストが走らず、キャッシュからデータを取得する
正确设置查询键
要有效使用React Query的缓存功能,除了正确设置staleTime之外,还需要管理好queryKey。queryKey是useQuery的第一个参数,它接受一个数组输入。例如,我们可以考虑一个接受filter查询参数的API。

在React Query中,我们将filter与queryKey一起传递给queryKey中的”issues”。通过接受与关键字符串一起动态变化的输入(如filter),React Query可以区分保存缓存。这样可以管理每个filter的缓存,因此正确设置queryKey变得非常重要。
由於非常重要,因此他們還提供了一個能夠檢查queryKey是否被正確管理的eslint插件。
总结
React Query不仅仅是一个数据获取库,而是一个用于异步状态管理的库。从像Redux这样的库同时管理客户端状态和服务器端状态的时代开始,随着服务器数据缓存库的出现,服务器端状态的责任被分割了出去。现在,通过React Query可以有效地进行异步管理,并且能够适当地更新状态。
以下为参考资料。
https://github.com/TanStack/query/discussions/4252 ↩