上一篇,深入理解了 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 中,有两种机制用于处理这种缓存,分别称为 QueryCache
和 MutationCache
。
-
QueryCache
:负责存储与查询相关的所有数据。这些数据可以是查询的数据,也可以是查询的当前状态。 -
MutationCache
:负责存储与突变相关的所有数据。这可以是突变的数据,也可以是其当前状态。
在使用 React Query 时,要做的第一件事就是创建一个 QueryClient 实例:
jsx
import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient()
QueryClient 中有很多配置,不传的时候,框架会适使用默认值。可以传递四个选项作为参数。它们如下:
- queryCache:全局使用的查询缓存。
- mutationCache:全局使用的突变缓存。
- logger:日志记录器,用于显示错误、警告和有用的调试信息。未指定时,React Query 将使用控制台对象。
- defaultOptions:默认选项,所有查询和突变在整个应用程序中使用的默认选项。
QueryCache
我们可以手动配置 QueryCache
或 MutationCache
,所有查询和突变在出现错误(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
,像这样: