一个数据获取竟被 React Query 玩出这么多花样来

本文是 React Query 系列第二篇,上一篇,我们概述了 React Query 是什么,以及它的 3 个核心概念,包括:

  1. 查询(Queries)
  2. 修改(Mutations),和
  3. 作废缓存(Query Invalidation)

本次,我们将深入第一个核心概念里的内容------useQuery()

useQuery() 是 React Query 对外提供一个用于封装获取数据请求的包装 React Hook。

jsx 复制代码
import { useQuery } from 'react-query'

function Example() {
  const { isLoading, isError, error, data } = useQuery('repoData', () =>
    fetch('https://api.github.com/repos/tannerlinsley/react-query').then(res =>
      res.json()
    )
  )

  if (isLoading) return 'Loading...'
  if (isError) return 'An error has occurred: ' + error.message

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.description}</p>
      <strong>👀 {data.subscribers_count}</strong>{' '}
      <strong>✨ {data.stargazers_count}</strong>{' '}
      <strong>🍴 {data.forks_count}</strong>
    </div>
  )
}

如你所见,useQuery 本身并不提供请求能力,而是依赖 Fetch API 或 axios 三方库提供请求能力,useQuery() 做的就是提供响应数据存储和请求状态包装。

接下来,我们就细细来看 useQuery() 的使用

useQuery() API

如果你有看到 useQuery() API 的官方定义,就会发现内容非常多。

不过,本文我们先只列最常用的一些。包括:

js 复制代码
import { useQuery } from 'react-query'

const {
  data,
  error,
  isError,
  isFetching,
  isLoading,
  isRefetching,
  isSuccess,
  refetch,
} = useQuery(queryKey, queryFn?, {
  enabled,
  onError,
  onSuccess,
  refetchOnWindowFocus,
  retry,
  select,
  staleTime,
})

当然,为了使用 useQuery(),我们还要通过 QueryClientProvider 注入 QueryClient 实例。这部分也是样板代码:

jsx 复制代码
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
import axios from 'axios'

const queryClient = new QueryClient()

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  )
}

useQuery() 有赖于 queryClient,这一步是必须的。

接下来,我们要举的案例都基于 <Example> 进行编写。开始吧!

基本案例

先来看一个最简单的例子。

jsx 复制代码
function Example() {
  const { isLoading, isError, error, data } = useQuery(
    'http200',
    () => axios.get('https://httpstat.us/200?sleep=3000')
  )

  if (isLoading) return 'Loading...'
  if (isError) return 'An error has occurred: ' + error.message

  return (
    <div>
      <p>{ JSON.stringify(data.data) }</p>
    </div>
  )
}

查看效果(3秒 Loading,最后展示数据):

这是使用 useQuery() 的最小可运行 DEMO 了。

错误重试

当然,useQuery() 还能拦截异常,我们将调用 URL 稍稍修改下。

jsx 复制代码
const { isLoading, isError, error, data } = useQuery(
  'http500',
  () => axios.get('https://httpstat.us/500')
)

查看效果:

你会看到 isLoading 状态会维持一段时间。这是因为,对于错误响应,useQuery() 默认会做重试。

查看控制台:

这里有 4 条一样的请求。useQuery 自带错误重试(Retries)机制,默认 3 次,加上原本的第 1 条,一共 4 条。

如果 4 次都失败了,状态就由 isLoading 变成 isError。

当然,你是可以通过 retry 选项自定义。

jsx 复制代码
useQuery('http200', () => axios.get('https://httpstat.us/500'), { retry: 1 })

再来看看效果:

重试 1 次失败后,就直接报错了。

retry 还支持设置布尔值:true 表示无限重试,false 表示不做任何重试。

缓存数据

另外,在使用 useQuery() 时,我们传入的第一个参数是 Query Key。Query Key 是 React Query 内部用来缓存获取数据的唯一标记,默认会缓存 5 分钟

那么如何体现呢?我们可以通过 useQuery() 做一次数据获取。

jsx 复制代码
function Example() {
  const { isLoading, isError, error, data, refetch } = useQuery(
    'http200',
    () => axios.get('https://httpstat.us/200?sleep=2000')
  )

  if (isLoading) return 'Loading...'
  if (isError) return 'An error has occurred: ' + error.message

  return (
    <div>
      <p>{ JSON.stringify(data.data) }</p>
      <button onClick={refetch}>refetch</button>
    </div>
  )
}

效果:

会发现,调用 refetch 的过程中,React Query 在控制台强制发起了一个请求。

不过在这个阶段,左侧页面的数据状态没有任何变动------这是由于 React Query 在接受值为 'http200' 的 Query Key 时,发现之前已经缓存过,所以就直接返回了缓存数据。

不过,我们也是有办法监听后台发起的这种请求的状态的,那就是通过 useQuery() 返回的 isFetching 变量。

jsx 复制代码
const { isLoading, isError, error, data, refetch, isFetching } = useQuery('http200', () => axios.get('https://httpstat.us/200?sleep=2000'))

再通过 isFetching 控制按钮状态。

jsx 复制代码
<button disabled={isFetching} onClick={refetch}>{ isFetching ? 'isFeching...' : 'refetch' }</button>

查看效果:

这样,我们就能监听后台默默进行的数据请求了。

当然,数据缓存时间是可以通过 cacheTime 选项自定义的(单位毫秒(ms))。

jsx 复制代码
useQuery('http500', () => axios.get('https://httpstat.us/500'), { cacheTime: 10 * 60 * 1000 })

以上,我们将缓存时间设定成 10 分钟。

默认后台数据的请求行为

useQuery() 还有一个默认行为,非常便捷,就是在网页从后台重新切入或者重新聚焦时,useQuery() 会自动触发后台数据重新获取

这个机制的原理是,React Query 会监听网页的 visibilitychange 和 focus 事件,当 document.visibilityState'visible' 或者触发 focus 事件时,就会重新请求。

当然,这个行为可以通过 refetchOnWindowFocus 选项禁用掉。

jsx 复制代码
useQuery(
  'http200',
  () => axios.get('https://httpstat.us/200'),
  { refetchOnWindowFocus: false }
)

注意,从 v5 版本开始,refetchOnWindowFocus 启用时(默认),不再监听 focus 事件,避免意外多出来的后台请求的发出。

过期时间

经过以上的学习,你可能会有疑问------既然 React Query 有默认帮我们缓存数据,为什么在缓存数据有效期范围内,还会发起新请求呢?

这就牵扯到另外一个概念,叫过期时间(Stale Time)。

useQuery() 默认获取到的数据,被认为是过期的。虽然重新获取数据时,会利用缓存数据,但只是用于临时展示,新请求获得的数据,会立即替代过期的缓存数据。

当然,这个行为是可以通过 staleTime 选项自定义(默认 staleTime: 0),其类型定义如下:

js 复制代码
staleTime: number | Infinity

当为数值时,单位毫秒(ms),表示数据有效/新鲜期。当 staleTime 设置为 Infinity 时,就表示数据永不过期。在缓存有效期范围内,useQuery() 会始终使用缓存数据,而不发起新的请求。

jsx 复制代码
useQuery(
  'http200', 
  () => axios.get('https://httpstat.us/200'),
  { staleTime: Infinity }
)

这样做之后,刷新页面,在获取到一次数据后,不管你如何重新切入/聚焦页面,都没有新的请求发出------这是因为数据在有效期内,另外还有缓存,就直接返回了。

staleTime 与 cacheTime

不过,staleTime 是如何与 cacheTime 配合起作用的呢?

我们再看一个例子:

jsx 复制代码
function Example() {
  const { isLoading, isError, error, data, refetch, isFetching } = useQuery('http200', () => axios.get('https://httpstat.us/200?sleep=2000'), { staleTime: Infinity, cacheTime: 5 * 1000})
  // ...
}

这里我们设置了数据永不过期,但缓存时间只有 5 秒钟。不过 5 秒钟过后,你重新切入页面,发现还是没有请求。为什么呢?

这是因为"缓存 5 秒钟",并不是说数据获取后,过 5 秒钟就删掉了!"缓存 5 秒钟"是指当查询缓存在不活跃或未使用,5 秒钟后数据就会被垃圾回收器回收

那么如何确定"useQuery 查询的缓存是不活跃或未使用"的呢?很简单的一个场景,就是使用 useQuery('http200') 的这个组件被卸载了。

我们改动下 App 组件。

jsx 复制代码
export default function App() {
  const [display, setDisplay] = useState(true)

  return (
    // Provide the client to your App
    <QueryClientProvider client={queryClient}>
      <>
        <button onClick={() => setDisplay(!display)}>{ display ? 'hide' : 'show' }</button>
        { display && <Example /> }
      </>
    </QueryClientProvider>
  )
}

<Example /> 数据渲染完成后。

我们再将 <Example /> 销毁,等 5 秒钟再展示<Example /> ------虽然设置了数据永不过期,但缓存时间只有 5 秒钟,现在缓存也没有(被回收了),因此就会发现新请求出来了。

正是因为 cacheTime 这个名称会让人产生歧义,因此从 v5 版本开始,cacheTime 选项改名为更符合其含义的 gcTime 了。

总结

本文我们讲解了 useQuery() API 的使用。

首先要知道的是,useQuery 本身并不提供请求能力,而是依赖 Fetch API 或 axios 三方库提供请求能力,useQuery() 做的就是提供响应数据存储和请求状态的包装。

在讲解了 useQuery() 基础使用后,我们介绍了围绕 useQuery() API 的一些核心内容,包括:

  1. 错误重试(Retries)机制:默认 3 次
  2. 数据缓存(Query Key):默认 5 分钟
  3. 过期时间(Stale Time):默认即过期

最后 staleTime 与 cacheTime 之间的区别是:staleTime 决定数据的新鲜度,cacheTime 则用于指定缓存数据。

值得注意的是,"缓存 5 秒钟"并不是说数据获取后,过 5 秒钟就删掉了,而是说查询缓存在不活跃(inactive)或未使用(unused),5 秒钟后数据就被垃圾回收器回收了。

当然限于篇幅,useQuery() 还有内容没讲,这个我们留在下次再说。

好了,希望本文的讲解能够对你有所帮助。感谢阅读,再见。

相关推荐
崔庆才丨静觅1 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax