一个数据获取竟被 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() 还有内容没讲,这个我们留在下次再说。

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

相关推荐
AlgorithmAce19 分钟前
Live2D嵌入前端页面
前端
nameofworld20 分钟前
前端面试笔试(六)
前端·javascript·面试·学习方法·递归回溯
前端fighter1 小时前
js基本数据新增的Symbol到底是啥呢?
前端·javascript·面试
流着口水看上帝1 小时前
JavaScript完整原型链
开发语言·javascript·原型模式
guokanglun1 小时前
JavaScript数据类型判断之Object.prototype.toString.call() 的详解
开发语言·javascript·原型模式
GISer_Jing1 小时前
从0开始分享一个React项目:React-ant-admin
前端·react.js·前端框架
川石教育1 小时前
Vue前端开发子组件向父组件传参
前端·vue.js·前端开发·vue前端开发·vue组件传参
Embrace9241 小时前
为什么 Vue2会出现数据更新视图不更新 Vue3不会出现
javascript·vue.js·ecmascript
qq_415628171 小时前
bpmn.js显示流程图
javascript·vue.js·流程图
GISer_Jing2 小时前
Vue前端进阶面试题目(二)
前端·vue.js·面试