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

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

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

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

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

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

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 的官方定义[2],就会发现内容非常多。

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

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 实例。这部分也是样板代码:

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> 进行编写。开始吧!

基本案例

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

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 稍稍修改下。

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 选项自定义。

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

再来看看效果:

图片图片

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

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

缓存数据

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

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

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 变量。

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

再通过 isFetching 控制按钮状态。

<button disabled={isFetching} notallow={refetch}>{ isFetching ? 'isFeching...' : 'refetch' }</button>

查看效果:

图片图片

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

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

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

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

默认后台数据的请求行为

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

图片图片

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

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

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

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

过期时间

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

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

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

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

staleTime: number | Infinity

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

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

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

staleTime 与 cacheTime

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

我们再看一个例子:

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 组件。

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` 选项改名[5]为更符合其含义的 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]概述了 React Query 是什么: https://juejin.cn/post/7378015213348257855

[2]官方定义: https://tanstack.com/query/v3/docs/framework/react/reference/useQuery

[3]不再监听 focus 事件: https://tanstack.com/query/v5/docs/framework/react/guides/migrating-to-v5#window-focus-refetching-no-longer-listens-to-the-focus-event

[4]意外多出来的后台请求: https://github.com/TanStack/query/pull/4805

[5]cacheTime 选项改名: https://tanstack.com/query/v5/docs/framework/react/guides/migrating-to-v5#rename-cachetime-to-gctime

责任编辑:武晓燕 来源: 写代码的宝哥
相关推荐

2021-01-19 05:29:41

代码Git 工作流

2021-03-26 10:48:14

代码语言提交

2021-10-11 08:21:23

@Valuespringspring框架

2022-01-25 12:14:39

面试try-catch代码

2021-09-01 05:41:03

Promise CLI项目

2022-09-14 12:00:51

React路由库前端

2024-05-16 20:48:23

ReactReact 19React 编译器

2021-09-28 12:25:30

数据库

2021-06-11 06:45:32

SQL结构化语言

2022-05-09 08:01:23

countdistinctMySQL

2022-09-27 10:52:25

Pythonprint函数

2021-05-27 07:54:21

JavaStateAQS

2022-09-21 10:45:42

局域网Mesh网络结构

2017-06-19 08:30:35

大数据数据可视化报表

2018-12-11 12:58:20

CPU散热器鳍片

2018-06-08 16:48:09

PythonQQ机器人

2016-11-14 14:52:47

vue.jses6javascript

2019-11-26 14:11:52

互联网裁员员工

2021-11-10 15:39:40

手机旗舰机中端机

2017-06-03 15:43:54

数据项目框架
点赞
收藏

51CTO技术栈公众号