【React Query】终极教程02:初见 RQ

上一篇,深入理解了 React 中的状态,这篇就来开启 React Query 的大门!


React Query 能干嘛?

RQ 能干的事儿多着呢 ~

而且,React Query 是用 TypeScript 写的,也可以与 React Native 结合使用,开箱即用,基本不需要任何配置,主要支持以下的一些 Features:

  • 缓存:每次查询后,数据将在可配置的时间内缓存,并可在整个应用程序中重复使用。
  • 取消查询:可以取消查询,并在取消后执行操作。
  • 乐观更新:在突变时,可以轻松更新状态,从而为用户提供更好的用户体验。如果突变失败,还可以轻松恢复到之前的状态。
  • 并行查询:如果需要同时执行一个或多个查询,可以毫无压力地执行,也不会影响缓存。
  • 依赖查询 :有时,我们需要在另一个查询完成后再执行另一个查询。React Query 可让这一切变得简单,并避免嵌套 Promise
  • 分页查询:有了 React Query,这种用户界面模式变得更加简单。你会发现,使用分页 API、更改页面和渲染获取的数据都非常简单。
  • 无限查询:React Query 使另一种用户界面模式变得更加简单。你可以在用户界面中实现无限滚动,相信 React Query 会让你在获取数据时更加轻松。
  • 滚动恢复:你是否有过这样的经历:从一个页面导航出去,当导航回来时,发现页面已经滚动到了导航之前的位置?这就是滚动恢复,只要缓存了查询结果,它就能正常工作。
  • 数据重新获取:需要触发数据重新获取?通过 React Query,你只需编写一行代码就能做到这一点。
  • 数据预取:有时,你可以提前确定用户的需求和下一步操作。在这种情况下,你可以相信 React Query 可以帮助你提前预取数据并为你缓存。这样,你的用户体验就会得到改善,你的用户也会更开心。
  • 跟踪网络模式和离线支持:你是否遇到过用户在使用你的应用程序时失去网络连接的情况?不用担心,因为 React Query 可以跟踪网络的当前状态,如果因为用户失去连接而导致查询失败,那么一旦网络恢复,查询就会重试。

是不是觉得很神奇?有了 React Query,这些日常需要大量第三方库支持和封装的功能一把给你梭哈了,可以大大减少模板代码量,提升代码的易读性,让你的页面如丝般顺滑。

使用 React Query

本系列将使用 React18.x + TypeScript 来学习 React Query,并且使用 vite 新建项目:

yaml 复制代码
pnpm create vite
# 然后按步骤选择 React + TypeScript 即可

安装 React Query:

yaml 复制代码
npm i @tanstack/react-query
# or
yarn add @tanstack/react-query
# or
pnpm add @tanstack/react-query

React Query 的配置非常简单。要将 React Query 集成到应用程序中,只需要两个东西:

  • QueryClient
  • QueryClientProvider

QueryClient

在 React Query 中,有两种机制用于处理这种缓存,分别称为 QueryCacheMutationCache

  • QueryCache:负责存储与查询相关的所有数据。这些数据可以是查询的数据,也可以是查询的当前状态。

  • MutationCache:负责存储与突变相关的所有数据。这可以是突变的数据,也可以是其当前状态。

在使用 React Query 时,要做的第一件事就是创建一个 QueryClient 实例:

jsx 复制代码
import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient()

QueryClient 中有很多配置,不传的时候,框架会适使用默认值。可以传递四个选项作为参数。它们如下:

  • queryCache:全局使用的查询缓存。
  • mutationCache:全局使用的突变缓存。
  • logger:日志记录器,用于显示错误、警告和有用的调试信息。未指定时,React Query 将使用控制台对象。
  • defaultOptions:默认选项,所有查询和突变在整个应用程序中使用的默认选项。

QueryCache

我们可以手动配置 QueryCacheMutationCache ,所有查询和突变在出现错误(onError)或执行成功(onSuccess)时都可以执行一些逻辑,也可以在突变执行前(onMutate)执行一些逻辑。

在 QueryCache 中可以这样做:

jsx 复制代码
import { QueryCache } from '@tanstack/react-query'

const queryCache = new QueryCache({
  onError: error => {
    // 处理 error 的情况
  },
  onSuccess: data => {
    // 处理 success 的情况
  }
})

MutationCache

MutationCache 非常相似:

jsx 复制代码
import { MutationCache } from '@tanstack/react-query'

const mutationCache = new MutationCache({
  onError: error => {
    // 处理 error 的情况
  },
  onSuccess: data => {
    // 处理 success 的情况
  },
  onMutate: newData => {
    // 处理突变之前的情况
  },
})

默认情况下,可能都不会处理全局的,一般是在实例化缓存对象时,在相应对象的相应函数中进行配置。

在配置好后,将该对象发送给 QueryClient,就可以实例化了一个新的 QueryClient:

jsx 复制代码
const queryClient = new QueryClient({
   mutationCache,
   queryCache
})

Logger

如果你希望在项目中,除了使用控制台对象之外,配置日志记录器,那么你需要在 QueryClient 中进行配置:

js 复制代码
const logger = {
   log: (...args) => {
      // 调用你自定义的日志函数
   },
   warn: (...args) => {
      // 调用你自定义的warn日志函数
   },
   error: (...args) => {
      // 调用你自定义的error日志函数
   },
 };

然后,将此日志记录器传递给 QueryClient

jsx 复制代码
const queryClient = new QueryClient({
   // ...
   logger
})

defaultOptions

defaultOptions 允许你覆盖这些默认值,默认值有很多,这里就不展示了,官方网站有。

以下是覆盖 defaultOptions 的方法:

jsx 复制代码
const defaultOptions = {
   queries: {
     staleTime: Infinity,
   },
 };

在这个 queries 对象中,我们指定所有查询的 staleTime 为 Infinity。设置完成后,所有查询的 staleTime 属性都将设置为 Infinity

jsx 复制代码
const queryClient = new QueryClient({
   // ...
   defaultOptions
})

QueryClientProvider

React Query 基于 React Context,创建了 QueryClientProvider 的自定义 Provider,这样就可以在被它包裹的组件中使用 React Query 相关的 API 了。

在项目中引入:

jsx 复制代码
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import CountProvider from "./components/CountProvider";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const root = ReactDOM.createRoot(document.getElementById("root"));

const queryClient = new QueryClient();

root.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <CountProvider>
        <App />
      </CountProvider>
    </QueryClientProvider>
  </React.StrictMode>
);

React Query Devtools

React Query 还提供了专门的开发工具 React Query Devtools,可以可视化地查看所有查询和突变的当前状态。安装:

yaml 复制代码
npm i @tanstack/react-query-devtools
# or
yarn add @tanstack/react-query-devtools
# or
pnpm add @tanstack/react-query-devtools

它有两种模式:浮动模式(Floating Mode)嵌入模式(Embedded Mode)

浮动模式

浮动模式将使 React Query 徽标浮动在屏幕一角。显示在屏幕角落的徽标如下:

打开后,你就会看到 Devtools:

Devtools 实际上会在 DOM 树中生成一个独立于 App 之外的 DOM 节点:

要在应用程序中添加浮动模式 Devtools,你需要导入它:

jsx 复制代码
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import CountProvider from "./components/CountProvider";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

const root = ReactDOM.createRoot(document.getElementById("root"));

const queryClient = new QueryClient();

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

嵌入模式

嵌入式模式会将 Devtools 嵌入式作为常规组件添加到页面中,挤占页面的空间:

引入:

jsx 复制代码
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import CountProvider from "./components/CountProvider";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools";

const root = ReactDOM.createRoot(document.getElementById("root"));

const queryClient = new QueryClient();

root.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <ReactQueryDevtoolsPanel />
      <CountProvider>
        <App />
      </CountProvider>
    </QueryClientProvider>
  </React.StrictMode>
);

你会看到:

通常来说,生产中不应该显示 Devtools。不过,你也可以在生产环境中加载它们,来调试代码。

在生产环境中使用 Devtools

如果你想在生产环境中加载 Devtools,则必须使用 延时加载,而不是用动态加载,这对于包体积的控制非常重要。懒加载 Devtools 也很重要,因为在生产环境中,我们可能永远都不会用到它,所以我们要避免在构建过程中添加我们根本用不上的东西。

要在 React 中懒加载组件,我们可以使用 React.lazy 来做:

jsx 复制代码
const ReactQueryDevtoolsProduction = React.lazy(() =>
  import('@tanstack/react-query-devtools/build/lib/index.prod.js').then(
    (d) => ({
      default: d.ReactQueryDevtools,
    }),
  ),
)

这样我们就可以在生产环境中懒加载它,而不会增加包的大小。

什么是动态导入?动态导入允许你在代码中任意位置异步加载模块,将返回一个Promise,成功后将返回一个包含模块导出的对象。

也可以像这样动态导入模块:

jsx 复制代码
const ReactQueryDevtoolsProduction = React.lazy(() =>
  import('@tanstack/react-query-devtools/production').then(
    (d) => ({
      default: d.ReactQueryDevtools,
    }),
  ),
)

在使用 React.lazy 并尝试渲染我们刚刚懒加载的组件时,必须要使用 Suspense 进行包裹,这样就会在懒加载的组件 loading 时显示 fallback 组件,进而提升用户体验。

jsx 复制代码
import React, { Suspense } from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import CountProvider from "./components/CountProvider";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const root = ReactDOM.createRoot(document.getElementById("root"));

const queryClient = new QueryClient();

const ReactQueryDevtoolsProduction = React.lazy(() =>
  import("@tanstack/react-query-devtools/build/lib/index.prod.js").then(
    (d) => ({
      default: d.ReactQueryDevtools,
    })
  )
);

root.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <Suspense fallback={null}>
        <ReactQueryDevtoolsProduction />
      </Suspense>
      <CountProvider>
        <App />
      </CountProvider>
    </QueryClientProvider>
  </React.StrictMode>
);

显然,我们不希望在渲染组件时自动加载 Devtools。那就还需要的是一个切换它的方法。由于这是一个生产构建,我们不希望在其中包含一个可能会让用户感到困惑的按钮。一种方法是在 window 对象中天假一个 toggleDevtools 函数。

React Query 文档建议我们这样做:

jsx 复制代码
import React, { Suspense, useEffect, useState } from "react";

const ReactQueryDevtoolsProduction = React.lazy(() =>
  import("@tanstack/react-query-devtools/build/lib/index.prod.js").then(
    (d) => ({
      default: d.ReactQueryDevtools,
    })
  )
);

const ReactQueryDevtools = () => {
  const [showDevtools, setShowDevtools] = useState(false);

  useEffect(() => {
    window.toggleDevtools = () =>
      setShowDevtools((previousState) => !previousState);
  }, []);

  return (
    showDevtools && (
      <Suspense fallback={null}>
        <ReactQueryDevtoolsProduction />
      </Suspense>
    )
  );
};

export default ReactQueryDevtools;

然后在 index.js 中使用:

js 复制代码
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import CountProvider from "./components/CountProvider";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import ReactQueryDevtools from "./components/ReactQueryDevToools";

const root = ReactDOM.createRoot(document.getElementById("root"));

const queryClient = new QueryClient();

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

这样,我们就可以通过控制台来切换 devtools,像这样:


相关推荐
维生素C++19 分钟前
【可变模板参数】
linux·服务器·c语言·前端·数据结构·c++·算法
vah10125 分钟前
python队列操作
开发语言·前端·python
项目題供诗25 分钟前
尚品汇-H5移动端整合系统(五十五)
java·服务器·前端
会蹦的鱼1 小时前
React学习day07-ReactRouter-抽象路由模块、路由导航、路由导航传参、嵌套路由、默认二级路由的设置、两种路由模式
javascript·学习·react.js
DT——6 小时前
Vite项目中eslint的简单配置
前端·javascript·代码规范
学习ing小白8 小时前
JavaWeb - 5 - 前端工程化
前端·elementui·vue
真的很上进8 小时前
【Git必看系列】—— Git巨好用的神器之git stash篇
java·前端·javascript·数据结构·git·react.js
胖虎哥er8 小时前
Html&Css 基础总结(基础好了才是最能打的)三
前端·css·html
qq_278063719 小时前
css scrollbar-width: none 隐藏默认滚动条
开发语言·前端·javascript
.ccl9 小时前
web开发 之 HTML、CSS、JavaScript、以及JavaScript的高级框架Vue(学习版2)
前端·javascript·vue.js