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进行数据获取都是可以的。

image.png

如果您能理解React Query并不是一个数据获取库,那么您就不会产生以下这样的疑问。

    • React QueryでどのようにbaseURLを設定するのか?

 

    • React Queryでどのようにresponseのheaderにアクセスするのか?

 

    React QueryでどのようにgraphQLのrequestを作成するのか?

关于这些问题只有一个答案。
“这不是React Query所关心的事情。”

React Query唯一期望的是返回一个Promise。

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。那么,为什么仅仅使用客户端状态管理会导致问题呢?原因在于客户端状态与服务器状态在状态的特征和角色上存在差异。我已经将它们的区别总结在下面的表格中。

客户端状态和服务器状态的区别

Client StateServer Stateクライアントが完全に所有しているリモートに操作されうる同期的に使用することができる非同期的に使用する必要がある常に最新の状態である状態が古くなる可能性がある

在中文中,以下是一个可能的翻译选项:

状态的所有权是由客户端或服务器拥有的。

首先,第一个区别是关于状态的所有者。对于客户端状态(例如UI状态等),客户端完全拥有状态。而用户信息、文章列表等数据则由服务器远程管理。

从根本上讲,拥有状态的实体在客户端和服务器端之间是不同的

状态是否同步还是异步

客户端状态是指所有权完全在客户端的状态,因此可以同步处理。而服务器状态涉及数据的获取和更新,因此需要异步管理。

在管理Server State时同步是困难的。

是否状态一直保持最新。

在中文中,可以这样表述:

最后,关键是要确保状态始终保持最新。客户端状态是不依赖外部的状态,因此可以保证始终是最新的,而服务器状态呢?当我们访问新闻网站时,不能保证信息在一小时后仍然是最新的。很可能会有新的文章更新或被删除。

换句话说,我们通过Client State管理的服务器状态仅仅是一个快照,不能保证始终是最新的状态。因此,我们有必要将责任分别放在Client State和Server State上,并确保适时地更新Server State。

服务器状态可能会变得过时

考虑以上的内容,我们可以发现在处理Server State时需要意识到上述的三点。让我们在有意识的基础上思考React Query的作用。

React Query 是一个用于管理异步状态(服务器状态)的库。

重新考虑一下,我们回到这个结论。刚才,我主张Client State和Server State由于各自的特点和角色不同,应该将责任分开。那么,我们应该如何在异步方式下确保Server State的状态保持最新并加以管理呢?

在这里,React Query派上了用场!

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.png
React Query只期望Promise
React Query可以高效地管理异步状态(如加载等)

充分利用现金

通过React Query,我们能够高效地管理异步状态。然而,在处理服务器状态时,还有一个重要的点需要考虑。那就是我们需要尽可能地将状态保持最新。React Query的缓存机制为我们提供了在适当的时机更新状态的选项。

有关React Query缓存的事项。

React Query使用queryKey作为useQuery的第一个参数来管理缓存。缓存数据保存在JavaScript内存中,因此如果浏览器重新加载,缓存会被清除。现在,我们整理一下使用useQuery发送API请求并获取数据(包括是否有缓存)的流程。


    1. 在组件中调用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。

image.png

在React Query中,我们将filter与queryKey一起传递给queryKey中的”issues”。通过接受与关键字符串一起动态变化的输入(如filter),React Query可以区分保存缓存。这样可以管理每个filter的缓存,因此正确设置queryKey变得非常重要。

正确管理queryKey将导致正确管理缓存。

由於非常重要,因此他們還提供了一個能夠檢查queryKey是否被正確管理的eslint插件。

 

总结

React Query不仅仅是一个数据获取库,而是一个用于异步状态管理的库。从像Redux这样的库同时管理客户端状态和服务器端状态的时代开始,随着服务器数据缓存库的出现,服务器端状态的责任被分割了出去。现在,通过React Query可以有效地进行异步管理,并且能够适当地更新状态。

以下为参考资料。

 

由于缓存时间(cacheTime)的命名和作用不太一致,因此在TansTack Query v5中,我们也可以看到改用gcTime作为名称的动向。
https://github.com/TanStack/query/discussions/4252 ↩
bannerAds