2
头图

缓存状态与调试工具

写在前面

由于国内较少有比较系统的react-query教程,因此笔者结合官方文档以及官方课程的内容,希望写一个较为全面的教程。本文将以各种例子作为切入点,尽可能通俗易懂地讲解相关知识点。如果有错误,还请大家在评论区指出,笔者会尽快改正。

目录

由于缓存状态处于幕后,难以直观地展现相关的变化,因此本章同时也会介绍调试工具的相关使用,来详细地说明,并且直观地展现相关状态的变化。

缓存状态

react-query通常在挂载组件时获取数据;在获取数据后,将数据存储到缓存中,并将该数据提供给组件使用。

在react-query获取数据的过程中,主要会经历以下三种状态:

  • loading
  • error
  • success

这三种状态(status)的流程关系如下:

graph TD
loading --> success
loading --> error

对于有开发经验的同学应该不难理解,请求接口后,要么成功,要么失败。

上面的三种状态其实也对应useQuery钩子中的isLoadingisSuccessisError属性。也可以通过status属性,获取到loadingsuccesserror

当react-query进行后端请求查询时,会有以下三个状态:

  • idle:空闲,表示当前不需要从后端获取数据
  • fetching: 获取数据,表示当前正在从后端获取数据
  • paused:暂停,表示原本尝试从后端获取数据,但是通常由于未联网的原因导致暂停

fetchStatus将会在idlefetchingpaused这三个状态间经历循环:

graph TD
idle --> fetching
fetching --> paused
paused --> fetching
fetching --> idle

📢注意

在react-query中statusloading状态(或者isLoadingtrue)指的是第一次从后端获取成功之前的状态,你可以查看这个在线例子来理解这个状态持续的周期:查看在线例子

fetchStatusfetching状态(或者isFetchingtrue)指的是每次从后端获取数据的加载状态(包含第一次获取数据)。

整个生命周期,举个例子:

假如你使用react-query从Github的接口请求了react的issue列表,你此次的请求结果将会在status中标记为successerror,或者从isSuccessisError中判断请求成功或者失败。

请求后端数据成功后,在写入缓存时,此时的缓存状态是fresh(最新)状态,但是很快(默认过期时间是0ms)就会变为stale(老旧)状态。

如果使用react的issue列表的每个组件都被卸载后,issue列表数据的缓存状态将会被标记为inactive(不活跃)状态。此时数据将不会被删除,直到一段时间后(默认为5分钟),react-query将会从缓存中删除该条数据。

在变为inactive(不活跃)状态之前,该条数据将会在fresh(最新)stale(老旧)之间来回切换,同时接口请求状态也会在idlefetching之间切换。

👇🏻下面将详细描述该例子过程的细节

使用DevTools来观察缓存状态变化

为了更加直观地观察缓存状态的变化,可以使用DevTools进行直观地展示。你需要在你的入口文件中,添加下面的代码

import * as React from 'react';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { QueryClient, QueryClientProvider } from 'react-query';
+ import { ReactQueryDevtools } from 'react-query/devtools';

import App from './App';

const rootElement = document.getElementById('root');
const root = createRoot(rootElement);
const queryClient = new QueryClient();

root.render(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
+      <ReactQueryDevtools initialIsOpen />
    </QueryClientProvider>
  </StrictMode>
);

上面的示例代码中,我们引入并设置了ReactQueryDevtools组件的initialIsOpen属性,因此将会默认打开调试工具

接着我们开始请求接口,你可以在调试工具中看到当前所有缓存状态的概览,如下图所示:

你也可以打开在线演示观察,在例子中点击【刷新】按钮,来观察缓存状态的变化。

状态概览

fresh(最新)数据及stale(老旧)数据

react-query是否会触发查询函数,并从后端接口获取数据,与缓存状态是:fresh(最新)状态或stale(老旧)状态有关。如果缓存状态是stale(老旧),表示该查询将会有资格重新获取,但如果缓存状态是fresh(最新)的,就不会重新获取。

注意📢

如果你想更好地控制接口请求的频率,请务必记住这些内容。

在默认情况下,后端返回的数据其缓存状态将会立即({staleTime: 0})从fresh(最新)状态变为stale(老旧)状态。

其实这样做不难理解,因为当你请求到数据后,后端的数据有可能就发生了变化,因此当拿到后端返回的数据瞬间,缓存状态就是stale(陈旧)的了。

你可以将配置中的staleTime,设置一个毫秒数的数字,那么缓存将会在staleTime毫秒后过期(从fresh(最新)变为stale(陈旧))

注意:此时staleTime不能为0,不然没有任何意义。

在下面的例子中,将staleTime设置为了60s,可以在DevTools中观察一下缓存状态:

点击查看在线演示

import * as React from 'react';
import { useQuery } from 'react-query';
import './style.css';

export default function App() {
  const getRepos = (username) =>
    fetch(`https://api.github.com/users/${username}/repos`).then((res) =>
      res.json()
    );

  const reposQuery = useQuery(
    ['repos', 'facebook'],
    () => getRepos('facebook'),
+    { staleTime: 1000 * 60 }
  );
  return (
    <div>
      <button
        onClick={() => {
          reposQuery.refetch();
        }}
      >
        刷新
      </button>
      <p>
        {reposQuery.isLoading && '首次加载中...'}
        {reposQuery.isFetching && '刷新中...'}
      </p>
      <p>{JSON.stringify(reposQuery.data)}</p>
    </div>
  );
}

如果把staleTime设置为Infinity,表示当前查询的数据将只会获取一次,且会在整个网页的生命周期内缓存。

现在应该很清楚地了解了,缓存状态是如何从fresh(最新)变为stale(老旧)的过程了。那么是数据一旦变为stale(老旧)就会重新获取么?并不是,而是需要满足一定的触发条件才可以。下面将介绍这些触发条件:

什么时候会触发重新获取数据操作?

在react-query中并不是缓存从fresh(最新)转换为stale(老旧)状态时,就会重新获取。而是依赖于以下五个触发条件重新获取数据数据:

①组件挂载时

当组件首次加载,将会触发数据的获取。如果组件被卸载后再次被加载,此时也会触发数据的重新获取。

②查询键改变时

在前面章节的示例中也提到过,当查询键改变时,将会自动触发数据的重新获取。

ps: 如果你的查询键中有对象,也不要担心react-query无法检测出来变化,因为react-query会进行深度比对。

③页面重新被聚焦

当用户把浏览器重新聚焦(比如在浏览器中打开了要调试的页面,之后切换到了vscode里面编辑代码,编辑完成后需要看下调试的页面效果,又切换到了浏览器),或者切换标签页(比如开了两个浏览器的选项卡,一个是百度,一个是你的页面,从百度的选项卡切换至你的页面)时,react-query都会自动重新获取数据。

这个触发条件是默认开启的,如果希望关闭这个触发条件,可以把refetchOnWindowFocus选项设置为false来禁止。

④网络重新连接

在当前用户断网重新联网后,react-query将会重新获取数据。比如你的用户拿着手机在地下通道中,信号中断导致断网,在用户从地下通道出来时,信号连接上后,又重新联网,此时数据将会重新获取。

这个触发条件同样是默认开启的。如果希望关闭这个触发条件,可以把refetchOnReconnect选项设置为false来禁止。

⑤定时刷新

这是一个需要你自己配置的一个触发条件。当你在配置中设置refetchInterval为数字(代表xxx毫秒)时。无论此时数据是fresh(最新)还是stale(老旧)的缓存状态,react-query都会在你设置的毫秒时间间隔内重新获取数据

以上五种就是react-query内置的重新获取触发条件。

注意❗️❗️❗️❗️📢

除了定时刷新外,其它的触发器,都需要状态是stale(陈旧)才可以触发,你可以在之前的例子中,尝试先失焦页面后,再聚焦(也就是③的触发条件),你会发现当数据在fresh(最新)状态时,并没有重新请求数据。

清理缓存

在缓存状态处于inactive(不活跃)状态或者使用这个查询数据的组件卸载时,超过5分钟(默认情况下)后,react-query将会自动清除该缓存。

如果你希望自定义这个时间,你可以使用cacheTime配置,下面的例子中将cacheTime设置为0,实现的效果是,当查询数据在inactive状态时,立即从缓存中删除。

const userQuery = useQuery(
  ["user", username],
  () =>
    fetch(`https://api.github.com/users/${username}`)
    .then(res => res.json()),
  { cacheTime: 0 }
);

当在缓存中删除查询数据后,此时的表现和这个查询数据从未加载过一样,在查询数据从后端返回前,将看到加载状态,获取到数据后,将看到查询数据。


修仙大橙子
108 声望17 粉丝

前端工程师