上一篇,我们对 useQuery
的使用做了非常详细的介绍,本篇将介绍一些高级的查询功能:
- 并行查询
- 分页查询
- 无限查询
并行查询
开发中经常会遇到并行查询的场景。并行查询是同时执行的查询,以避免出现顺序网络请求,通常称为网络瀑布。这对于前端性能优化非常重要,因为并行查询通过同时执行所有请求来避免网络瀑布。
并行查询主要有两种方式:
- 手动
- 动态
手动并行查询
如果现在让你手动做并行查询,根据之前的知识,你可能就会这么做:
jsx
import { useQuery } from "@tanstack/react-query";
import "../App.css";
import { fetchData } from "../api";
import { Table } from "antd";
function ManualParallelQueries() {
const { data: p_data_1 } = useQuery({
queryKey: [{ queryIdentifier: "api", q: "iPhone" }],
queryFn: fetchData,
});
const { data: p_data_2 } = useQuery({
queryKey: [{ queryIdentifier: "api", q: "OPPO" }],
queryFn: fetchData,
});
const { data: p_data_3 } = useQuery({
queryKey: [{ queryIdentifier: "api", q: "MacBook" }],
queryFn: fetchData,
});
const products = [
p_data_1?.products ?? [],
p_data_2?.products ?? [],
p_data_3?.products ?? [],
];
const columns = [
{
title: "名称",
dataIndex: "title",
key: "title",
},
{
title: "价格",
dataIndex: "price",
key: "price",
},
{
title: "品牌",
dataIndex: "brand",
key: "brand",
},
];
return (
<>
{products.map((item) => (
<Table dataSource={item} columns={columns} rowKey={"id"} />
))}
</>
);
}
export default ManualParallelQueries;
查看结果:

动态并行查询
虽然手动并行查询也能 cover 大多数场景,但如果你的查询次数发生变化,那就无法在遵守 Hooks 规则的情况下使用它。为了解决这个问题,React Query 创建了一个 useQueries
自定义钩子。
useQueries
可以让你动态调用任意数量的查询。语法如下:
jsx
const queryResults = useQueries({
queries: [
{ queryKey: ["api", "queryOne"], queryFn: fetchData },
{ queryKey: ["api", "queryTwo"], queryFn: fetchData }
]
})
queries
属性中接收一个查询数组。而且每个查询还可以设置自己的 options,在这里有一个心智模型:即这些查询也可以像单个 useQuery
查询一样进行自定义。useQueries
钩子将返回一个包含所有查询结果的数组。
了解了 useQueries 的工作原理,来实战一下:
jsx
import { useQueries } from "@tanstack/react-query";
import "../App.css";
import { fetchData } from "../api";
import { Table } from "antd";
const params: Array<string> = ["iPhone", "OPPO", "MacBook"];
function DynamicParallelQueries() {
const multipleQueries = useQueries({
queries: params.map((q: string) => {
return {
queryKey: [{ queryIdentifier: "api", q }],
queryFn: fetchData,
};
}),
});
const columns = [
{
title: "名称",
dataIndex: "title",
key: "title",
},
{
title: "价格",
dataIndex: "price",
key: "price",
},
{
title: "品牌",
dataIndex: "brand",
key: "brand",
},
];
return (
<>
{multipleQueries.map((item: any) =>
item.isFetching ? (
"Fetching data..."
) : (
<Table
dataSource={item?.data?.products ?? []}
columns={columns}
rowKey={"id"}
/>
)
)}
</>
);
}
export default DynamicParallelQueries;
查看页面:

是不是非常简单且优雅!
深入 QueryClient
前面已经说了,QueryClient 可以操作缓存数据,触发重新获取查询。QueryClient 可以用来做更多事情,例如以下问题:
- 查询失效
- 预取(Prefetching)
- 查询取消
在组件中可以利用
useQueryClient
这个钩子来使用QueryClient
。
查询过滤器
在开始学习查询失效之前,有一点要注意,其中一些方法,也就是我们将要看到的方法,可以接收某些查询过滤器,以帮助你匹配正确的查询。
来看这个示例:
jsx
import { Button } from "antd";
import DynamicParallelQueries from "./components/DynamicParallelQueries";
import { useQueryClient } from "@tanstack/react-query";
// import ManualParallelQueries from "./components/ManualParallelQueries";
function App() {
const queryClient = useQueryClient();
const handleOnRefetch = () => {
queryClient.refetchQueries({ queryKey: [{ queryIdentifier: "api" }] });
};
return (
<>
<Button onClick={handleOnRefetch} type="primary">
RefetchQueries
</Button>
{/* <ManualParallelQueries /> */}
<DynamicParallelQueries />
</>
);
}
export default App;

向 refetchQueries
方法提供过滤器,可以重新获取所有符合查询关键字["api"]
或以查询关键字["api"]
开头的查询。
现在,除 queryKey
外,还可以使用更多过滤器。在 QueryClient 方法中使用的过滤器通常称为 QueryFilters
,支持通过以下方式进行过滤:
- 查询 Keys
- 查询 type
- 查询是过时的还是新鲜的
- fetchStatus
- predicate函数
下面是一些使用 QueryFilters 的示例:
jsx
queryClient.refetchQueries({ type: "active" })
在下面的示例中,重新获取所有现在被视为过期(staleTime
已过期)的查询:
jsx
queryClient.refetchQueries({ stale: true })
在下面的示例中,我们使用以值为"idle"
的 fetchStatus 过滤器来重新获取当前没有获取任何内容的所有查询:
jsx
queryClient.refetchQueries({ fetchStatus: "idle"})
使用 predicate
属性并向其传递一个匿名函数,该函数将接收正在验证的查询,并访问其当前状态;如果该状态为 "error"
,则函数将返回 true,意思是当前状态为 error
的所有查询都将重新获取:
jsx
queryClient.refetchQueries({
predicate: (query) => query.state.status === "error"
})
还可以组合过滤器,重新获取了查询关键字以["api"]
开头的所有过时查询。:
jsx
queryClient.refetchQueries({ queryKey: ["api"], stale: true })
如果不想传递任何过滤器,并希望该方法适用于所有查询,则可以选择不传递任何过滤器:
jsx
queryClient.refetchQueries()
这将重新获取所有查询。
查询失效
有时,数据过期可能与配置的 staleTime
无关,因为可能是你执行了突变(Mutation
)操作,也有可能是其他用户做了类似的操作。这时,可以利用 QueryClient
的 invalidateQueries
方法将查询标记为过期。
下面是 invalidateQueries
方法的语法:
jsx
queryClient.invalidateQueries({ queryKey: ["api"] })
通过调用 invalidateQueries
,所有与["api"]
匹配或以["api"]
开头的查询都会被标记为过期,如果配置了 staleTime ,则会覆盖其过期时间。如果你的查询因为渲染的 useQuery 钩子正在使用而处于活跃状态,那么 React Query
将负责重新获取该查询。
现在让我们通过下面的示例来实践一下:
jsx
import { Button, Space } from "antd";
import DynamicParallelQueries from "./components/DynamicParallelQueries";
import { useQueryClient } from "@tanstack/react-query";
// import ManualParallelQueries from "./components/ManualParallelQueries";
function App() {
const queryClient = useQueryClient();
const handleOnRefetch = () => {
queryClient.refetchQueries({ queryKey: [{ queryIdentifier: "api" }] });
};
const handleOnQueryInvalidation = () => {
queryClient.invalidateQueries({
queryKey: [{ queryIdentifier: "api" }],
});
};
return (
<>
<Space>
<Button onClick={handleOnRefetch} type="primary">
重新查询
</Button>
<Button onClick={handleOnQueryInvalidation} type="primary">
查询失效
</Button>
</Space>
{/* <ManualParallelQueries /> */}
<DynamicParallelQueries />
</>
);
}
export default App;

点击查询失效
按钮将使所有匹配或包含 [{ queryIdentifier: "api" }]
作为其 queryKey
一部分的查询无效,查询数据将立即被标记为过时数据,将在后台自动重新请求。
数据预取
当你可以预测用户可能想做的事情必然会触发查询时,你就可以利用 prefetchQuery
预取查询,为用户节省一些时间。
以下是 prefetchQuery
方法的语法:
jsx
queryClient.prefetchQuery({
queryKey: ["api"],
queryFn: fetchData
});
prefetchQuery
尝试获取数据,并将其缓存到给定的 queryKey
下。这是一个异步方法,因此需要使用 async/await
。
来一个实际例子:
jsx
import { Button, Space } from "antd";
import { useQueryClient } from "@tanstack/react-query";
import { fetchData } from "./api";
import { useState } from "react";
import PrefetchedDataComponent from "./components/PrefetchedDataComponent";
function App() {
const [renderComponent, setRenderComponent] = useState(false);
const queryClient = useQueryClient();
const handleOnPrefetchData = async () => {
await queryClient.prefetchQuery({
queryKey: [{ queryIdentifier: "api", q: "Samsung" }],
queryFn: fetchData,
staleTime: 60000,
});
};
return (
<>
<Space>
<Button
onMouseEnter={handleOnPrefetchData}
onClick={() => setRenderComponent(true)}
>
预取查询
</Button>
</Space>
{renderComponent ? <PrefetchedDataComponent /> : null}
</>
);
}
export default App;
我们在 prefetchData
函数中调用 prefetchQuery
方法,并将返回的数据缓存在 [{ queryIdentifier: "api", q: "Samsung" }]
queryKey
下。我们还将 staleTime
设为 1 分钟,因此在调用此查询后,数据将在 1 分钟内保持新鲜。
PrefetchedDataComponent
组件:
jsx
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { fetchData } from "../api";
import { Table } from "antd";
const PrefetchedDataComponent = () => {
const queryClient = useQueryClient();
const eData = queryClient.getQueryData([
{ queryIdentifier: "Samsung_api", q: "Samsung" },
]) as any;
const _products = eData?.products ?? [];
const { data } = useQuery({
queryKey: [{ queryIdentifier: "Samsung_api", q: "Samsung" }],
queryFn: fetchData,
enabled: _products.length === 0,
});
const columns = [
{
title: "名称",
dataIndex: "title",
key: "title",
},
{
title: "价格",
dataIndex: "price",
key: "price",
},
{
title: "品牌",
dataIndex: "brand",
key: "brand",
},
];
return (
<Table dataSource={data?.products ?? []} columns={columns} rowKey={"id"} />
);
};
export default PrefetchedDataComponent;
-
我们预测用户点击按钮会希望获取数据,所以,我们让鼠标移入的时候触发数据预取。数据预取完成后,将缓存在
[{ queryIdentifier: "api", username: "userOne" }]
queryKey
下。 -
当用户点击按钮时,
PrefetchedDataComponent
组件渲染。由[{ queryIdentifier: "api", username: "userOne" }]
queryKey
标识的 useQuery 钩子已经缓存了数据并将其标记为新鲜数据,持续时间为一分钟,因此不需要触发数据请求,用户看到的是预取数据的渲染。
React Query 官方也提供了一个非常好的例子:传送门
取消查询
有时,你可能希望 useQuery 钩子在进行查询时可以取消。默认情况下,一旦你的 Promise resolve
,该查询数据仍将被返回和缓存。但有时候,出于某种原因,你的组件在数据请求中途卸载,你可能希望取消查询。React Query 可以通过 自动或者手动
取消查询来解决这个问题。
为了实现取消查询,React Query 使用了 signal
,它可以与 DOM 请求通信并中止它们,也就是 AbortSignal
对象,它源自于 AbortController Web API
(AbortController - Web APIs | MDN)。
AbortSignal
由 QueryFunctionContext
注入到我们的查询函数中,然后由我们的数据获取客户端使用。
以下是我们如何利用 axios
的 AbortSignal
:
jsx
const fetchData = async ({ queryKey, signal }) => {
const { username } = queryKey[0];
const { data } = await axios.get(
`https://danieljcafonso.builtwithdark.com/react-query-api/${username}`,
{ signal }
);
return data;
};
我们从 QueryFunctionContext 接收 signal,并在进行 get 请求时将其传入 axios client。
如果在使用 GraphQL 的场景中使用 fetch 或 graphql-request 等 axios 的替代方法,也需要向客户端传递 AbortSignal。这就是使用 fetch 的方法:
jsx
const fetchDataWithFetch = async ({ queryKey, signal }) => {
const { username } = queryKey[0];
const response = await fetch(
`https://danieljcafonso.builtwithdark.com/react-query-api/${username}`,
{ signal }
);
if (!response.ok) throw new Error("Something failed in your request");
return response.json();
};
如果你使用的是 graphql-request 等 GraphQL client,也可以这样做:
jsx
const fetchGQL = async ({signal}) => {
const endpoint = <Add_Endpoint_here>;
const client = new GraphQLClient(endpoint);
const {
posts: { data },
} = await client.request({document: customQuery, signal});
return data;
};
手动取消查询
要手动取消查询,QueryClient 提供了 cancelQueries 方法。下面是 cancelQueries 方法的语法:
jsx
queryClient.cancelQueries({ queryKey: ["api"] })
调用 cancelQueries 后,当前正在获取并已收到 AbortSignal
的所有与["api"]
匹配或以["api"]
开头的查询都将被终止。
自动取消查询
当使用钩子的组件卸载,而你的查询正在获取数据时,如果你向 client 传递 AbortSignal,React Query 将通过取消承诺来中止你的查询。
让我们通过下面的示例看看 React Query 如何利用 AbortSignal 取消查询。首先,我们从配置查询函数开始:
jsx
const fetchData = async ({ queryKey, signal }) => {
const { username } = queryKey[0];
const { data } = await axios.get(
`https://danieljcafonso.builtwithdark.com/react-query-api/${username}`,
{ signal }
);
return data;
};
在前面的代码段中,我们创建了一个 fetchData 函数,该函数将接收 QueryContextObject。我们从中获取 signal,并将其传递给我们的 axios 客户端。
现在,让我们看看我们的组件:
jsx
const ExampleQueryCancelation = () => {
const [renderComponent, setRenderComponent] = useState(false);
return (
<div>
<button onClick={() => setRenderComponent(!renderComponent)}>
Render Component
</button>
{renderComponent ? <QueryCancelation /> : null}
</div>
);
};
在前面的代码段中,我们创建了一个 fetchData 函数,该函数将接收 QueryContextObject。我们从中获取信号,并将其传递给我们的 axios 客户端。
现在,让我们看看我们的组件:
jsx
const QueryCancelation = () => {
const { data } = useQuery({
queryKey: [{ queryIdentifier: "api", username: "userOne" }],
queryFn: fetchData,
});
const queryClient = useQueryClient();
return (
<div>
<p>{data?.hello}</p>
<button
onClick={() =>
queryClient.cancelQueries({
queryKey: [{ queryIdentifier: "api" }],
})
}
>
Cancel Query
</button>
</div>
);
};
该代码段向我们展示了 QueryCancelation 组件。在该组件中,我们将执行以下操作:
- 我们创建一个由
[{ queryIdentifier: "api", username: "userOne" }]
查询键标识的查询。 - 我们访问 QueryClient。
- 我们从查询中呈现数据。
- 我们将呈现一个按钮,点击该按钮后,QueryClient 将取消所有键值匹配或包含
[{ queryIdentifier: "api" }]
的查询。
现在让我们回顾一下这些组件的生命周期以及取消查询的工作原理:
- 我们渲染 ExampleQueryCancelation 组件。
- 我们点击按钮来呈现 QueryCancelation 组件。
- QueryCancelation 呈现后,其 useQuery 钩子将触发一个请求,以获取其数据。
- 在这个请求过程中,我们点击右后方的按钮来呈现 QueryCancelation。
- 由于我们的请求尚未解决,而且我们的组件已卸载,React Query 会终止我们的信号,从而取消我们的请求。
- 我们点击按钮,再次渲染 QueryCancelation 组件。
- QueryCancelation 已呈现,其 useQuery 钩子将触发请求以获取其数据。
- 在请求过程中,我们点击按钮取消查询。这将迫使 React Query 中止我们的信号,并再次取消请求。
至此,我们已经了解了 QueryClient 及其部分方法如何帮助我们解决一些常见的服务器状态难题。
分页查询
在处理大型数据集的接口时,为了避免让前端一次性处理所有数据,分页请求是一种常用的方法。只需使用 useQuery 及其选项之一 keepPreviousData 即可。
让我们看看接下来的示例,然后了解分页和 React Query 是如何工作的。首先,我们从查询函数开始:
jsx
const fetchData = async ({ queryKey }) => {
const { page } = queryKey[0];
const { data } = await axios.get(
`https://danieljcafonso.builtwithdark.com/react-query-paginated?page=${page}&results=10`
);
return data;
};
然后,构建一个用于显示和获取分页数据的组件:
jsx
const PaginatedQuery = () => {
const [page, setPage] = useState(0);
const { isLoading, isError, error, data, isFetching, isPreviousData } =
useQuery({
queryKey: [{ queryIdentifier: "api", page }],
queryFn: fetchData,
keepPreviousData: true,
});
if (isLoading) {
return <h2>Loading initial data...</h2>;
}
if (isError) {
return <h2>{error.message}</h2>;
}
return (
<>
<div>
{data.results.map((user) => (
<div key={user.email}>
{user.name.first}
{user.name.last}
</div>
))}
</div>
<div>
<button
onClick={() => setPage((oldValue) => oldValue === 0
? 0 : oldValue - 1)}
disabled={page === 0}
>
Previous Page
</button>
<button
disabled={isPreviousData}
onClick={() => {
if (!isPreviousData) setPage((old) => old + 1);
}}
>
Next Page
</button>
</div>
{isFetching ? <span> Loading...</span> : null}
</>
);
};
让我们按顺序回顾一下前面代码块中发生的事情:
- 我们创建一个状态变量来保存当前选定的页面。
- 我们创建查询,其中包含 [{ queryIdentifier: "api", page }] 查询键、作为查询函数的 fetchData 函数,并将 keepPreviousData 设置为 true。我们将该选项设置为 true,是因为默认情况下,只要我们的查询键发生变化,查询数据也会随之变化;现在,由于我们有一个分页的 API,所以即使我们更换页面,我们也希望继续显示数据。
- 然后,我们对 isLoading、isError、error、data、isFetching 和 isPreviousData 进行解构。
- 然后,我们有两个 if 语句,分别用于显示查询正在加载或出现错误。
- 如果有数据,我们将显示数据和两个按钮,分别用于移动到下一页和上一页。移动到下一页的按钮利用 isPreviousData 来确保在我们点击它并移动到下一个查询后,该按钮将被禁用。我们还会显示一个获取指示器。
既然我们已经了解了代码的结构,那就来看看它在交互时的表现吧:
- 我们的组件被渲染,并开始获取第一个页面。
isLoading 属性被设置为 true,因此我们渲染了 Loading 初始数据。
-
第一页的数据已解析,因此我们要显示它。
-
我们点击 "下一页 "按钮:
- 页面值递增。
- 查询键发生变化,因此下面的查询开始获取数据。
- 由于我们将 keepPreviousData 设为 true,因此仍将显示旧数据。
- 由于我们显示的是旧数据,因此 isPreviousData 设置为 true,下一页按钮也被禁用。
- 获取指示器显示正在加载。
-
我们获取并显示新数据。
-
点击上一页按钮:
- 页面值会递减。
- 查询键回到上一页。
- 由于数据已缓存在该查询键下,因此会返回。
- 由于数据已经过时,因此会触发新的获取请求。
- 获取指示器显示 "加载中"。
-
接收并显示新数据。
正如你所看到的,你只需要一个 keepPreviousData 和 useQuery,就能实现分页功能。
无限查询
另一种非常常见的需求是无限滚动组件,我们会看到一个列表,它允许我们在向下滚动时加载更多数据。React Query 提供了 useQuery 的替代方法,即另一个名为 useInfiniteQuery 的自定义钩子。
使用 useInfiniteQuery 钩子与 useQuery 钩子有许多相似之处,但也有一些不同之处需要我们注意:
-
你的数据现在是一个包含以下内容的对象:
- 获取的页面
- 用于获取页面的页面参数
-
用于获取下一页的名为 fetchNextPage 的函数
-
用于获取上一页的名为 fetchPreviousPage 的函数
-
一个名为 isFetchingNextPage 的布尔值状态,用于指示是否正在获取下一个页面
-
一个名为 isFetchingPreviousPage 的布尔值状态,表示正在获取下一页内容
-
一个名为 hasNextPage 的布尔值状态,用于指示列表是否有下一页面
-
一个名为 hasPreviousPage 的布尔值状态,用于指示列表是否有上一页
最后两个布尔值取决于可以传递给钩子的两个选项。它们分别是 getNextPageParam
和 getPreviousPageParam
。这两个函数将负责选取缓存中的最后一页或第一页,并检查其数据是否指示要获取的下一页或上一页。如果这些值存在,则相应的布尔值为 true。如果返回未定义值,则布尔值为false。
要使用 useInfiniteQuery 挂钩,需要这样导入:
jsx
import { useInfiniteQuery } from "@tanstack/react-query"
现在让我们看一个如何使用 useInfiniteQuery 挂钩构建无限列表的示例:
jsx
const fetchData = async ({ pageParam = 1 }) => {
const { data } = await axios.get(
`https://danieljcafonso.builtwithdark.com/react-query-infinite?page=${pageParam}&results=10`
);
return data;
};
钩子将在 QueryFunctionContext 中传递 pageParam,以便我们利用它来获取数据。与 useQuery 钩子中的查询函数一样,该查询函数需要解析数据或抛出错误,因此之前学到的所有原则都适用。
下一个代码段将展示我们的 InfiniteScroll 组件:
jsx
const InfiniteScroll = () => {
const {
isLoading,
isError,
error,
data,
fetchNextPage,
isFetchingNextPage,
hasNextPage,
} = useInfiniteQuery({
queryKey: ["api"],
queryFn: fetchData,
getNextPageParam: (lastPage, pages) => {
return lastPage.info.nextPage;
},
});
if (isLoading) {
return <h2>Loading initial data...</h2>;
}
if (isError) {
return <h2>{error.message}</h2>;
}
return (
<>
<div>
{data.pages.map((page) =>
page.results.map((user) => (
<div key={user.email}>
{user.name.first}
{user.name.last}
</div>
))
)}
</div>
<button
disabled={!hasNextPage || isFetchingNextPage}
onClick={fetchNextPage}
>
{isFetchingNextPage
? <<Loading...>>
: hasNextPage
? <<Load More>>
: <<You have no more data>>}
</button>
</>
);
};
在前面的代码段中,我们有一个组件,可以渲染一个无限列表。这就是我们在组件中要做的事情:
-
我们创建了 useInfiniteQuery,它以 ["api"] 作为查询关键字,以 fetchData 作为查询函数。它还在 getNextPageParam 选项中接收了一个匿名函数,用于检查下一页是否还有更多数据需要加载。
-
我们还从钩子中重组了一些构建应用程序所需的变量。
-
然后,我们使用两个 if 语句来显示查询正在加载或出现错误。
-
有了数据后,我们就将内容映射到页面属性中,以呈现我们的列表。
-
我们还将呈现一个按钮,如果没有下一页,或者正在获取下一页时,该按钮将被禁用。点击后,该按钮将获取更多数据。这个按钮的信息也将取决于一些约束条件: 2. 如果我们正在获取数据,下一页将显示加载信息 3. 如果我们有下一个页面,它将显示 "加载更多",以便用户点击它开始获取数据 4. 如果没有更多数据可提取,它将显示一条信息,让用户知道没有更多数据了
刚才我们回顾了组件的构建过程,现在让我们看看在与组件交互时它将如何工作:
-
我们的组件渲染后,会自动获取列表的第一页:isLoading 属性设置为 true,因此我们渲染正在加载的初始数据
-
列表第一页的数据已解析,因此我们将其显示出来。
-
同时,getNextPageParam 函数会检查列表中是否有更多数据。
-
如果没有更多数据,则 hasNextPage 属性将设为 false,获取更多数据的按钮将被禁用,并显示 "您没有更多数据"。
-
如果有更多数据,则 hasNextPage 属性设置为 true,用户可以点击按钮获取更多数据。
-
如果用户点击了按钮,我们将看到以下内容:
- 下一页开始获取。
- isFetchingNextPage 的值变为 true。
- 按钮被禁用,并显示加载信息。
- 数据已解析,我们的数据页属性长度增加,因为它拥有新页面的数据。重复步骤 3、4 和 5。
用 Devtools 调试查询
在之前,我们已经学习了 React Query Devtools。当时,你还不知道如何使用查询,所以我们无法看到它的工作原理,现在我们可以了。
我们将利用在 "动态并行查询" 部分展示 useQueries 钩子示例时编写的代码,代码如下:
jsx
const usernameList = ["userOne", "userTwo", "userThree"];
const ExampleTwo = () => {
const multipleQueries = useQueries({
queries: usernameList.map((username) => {
return {
queryKey: [{ queryIdentifier: "api", username }],
queryFn: fetchData,
};
}),
});
return (
<div>
{multipleQueries.map(({ data, isFetching }) => (
<p>{isFetching ? "Fetching data..." : data.hello}
</p>
))}
</div>
);
};
使用该代码并检查我们的页面时,Devtools 会向我们展示以下内容:

在上图中,我们可以看到以下内容:
- 我们有三个查询
- 每个查询都由各自的查询密钥标识
- 所有查询当前都已过时
- 我们选择了以
[{ queryIdentifier: "api", username: "userThree" }]
查询键标识的查询
选择查询后,我们可以在 "查询详情 "选项卡中看到查询详情。
在上图中,我们可以看到该查询由其查询密钥和状态标识。
在查询详细信息选项卡上向下滚动,我们还可以看到以下内容:

在上图中,我们可以看到可以对所选查询执行多种操作,如重新获取、失效、重置和删除。
我们还可以查看该查询的当前数据。
进一步向下滚动 "查询详细信息"选项卡,我们还可以查看 "查询资源管理器":

在上图中,我们可以看到所选查询的查询资源管理器。在这里,我们可以看到查询正在使用的选项。在这里,我们可以看到该查询的默认 cacheTime 为 300000。
现在你已经知道在 Devtools 中可以看到每个选定查询的内容了。
在结束本节之前,让我们看看点击查询详细信息操作中的某个按钮会发生什么:

在上图中,我们点击了由 [{ queryIdentifier: "api", username: "userTwo" }]
查询键标识的查询的无效按钮。
正如你在学习查询失效时所记得的,当我们失效一个查询时,它会被自动标记为过时查询,如果该查询当前正在渲染,则会被自动重新刷新。如图所示,情况就是这样。我们的查询已经失效,所以没有必要再次将其标记为失效,但由于它当前正在我们的页面上呈现,React Query 会负责重新抓取它,我们可以从图中看到这一点。
正如您在本节中所看到的,Devtools 可以为您节省大量调试查询的时间。通过查看查询内部,您可以检查其数据是否配置了正确的选项,甚至可以根据需要触发一些操作。
总结
在本章中,我们进一步了解了如何使用 useQuery 钩子来解决处理服务器状态时遇到的一些常见问题。现在,你可以轻松处理所有数据获取需求了。
你了解了并行查询,并学会了使用 useQuery 手动创建这些查询。我们还向你介绍了 useQuery 钩子的一种替代方法:useQueries。通过它,你学会了如何构建动态并行查询。
您将进一步了解 QueryClient 的一些方法,这些方法允许您预取、取消和失效查询,您还将了解如何利用 QueryFilters 来定制这些方法中使用的查询匹配。
分页是一种典型的 UI 模式,现在您知道了在 useQuery 及其选项的帮助下可以轻松创建分页组件。
另一种典型的用户界面模式是无限滚动。在另一个名为 useInfiniteQuery 的 useQuery 变体的帮助下,您了解了 React Query 如何让您构建一个具有无限列表的应用程序。
最后,您使用 React Query Devtools 查看了查询内部,了解了它如何让您调试查询并改进开发流程。
在第 6 章 "使用 React 查询执行数据突变 "中,我们将抛开数据获取,转而讨论数据突变。您将了解 React Query 如何在名为 useMutation 的自定义钩子的帮助下执行突变。您还将利用这个钩子来处理应用程序中更常见的服务器状态挑战,并开始在应用程序中使用乐观更新来构建更好的用户体验。