这是使用React Query与TypeScript的系列文章中的第三篇。
以前的帖子:
这篇文章将介绍如何用React Query来翻阅星球大战的人物集合,提供流畅的用户体验:

查询客户端提供者
React Query需要在使用它的组件上面有一个QueryClientProvider 组件:
import { QueryClient, QueryClientProvider } from "react-query";
...
const queryClient = new QueryClient();
render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>,
rootElement
);
我们在组件树的顶部添加了QueryClientProvider ,以使React Query对任何组件可用。
抓取功能
我们的获取函数如下:
async function getData(params: { queryKey: [string, { page: number }] }) {
const [, { page }] = params.queryKey;
const response = await fetch(`https://swapi.dev/api/people/?page=${page}`);
if (!response.ok) {
throw new Error("Problem fetching data");
}
const data = await response.json();
assertIsCharacterResponse(data);
return data;
}
查询键被传入该函数,它是一个元组,第二元素包含要请求的数据的页码。
该函数发出一个简单的请求,如果不成功就会引发一个错误。
数据的类型用assertIsCharacterResponse type assert函数断定为CharacterResponse :
type CharacterResponse = {
results: Character[];
next: string;
previous: string;
};
type Character = {
name: string;
};
function assertIsCharacterResponse(
response: any
): asserts response is CharacterResponse {
if (
!("results" in response && "next" in response && "previous" in response)
) {
throw new Error("Not results");
}
if (response.results.length > 0) {
const firstResult = response.results[0];
if (!("name" in firstResult)) {
throw new Error("Not characters");
}
}
}
查询
useQuery 钩子可以在React组件中使用,如下所示,以调用获取的函数:
import { useQuery } from "react-query";
...
export function App() {
const [page, setPage] = React.useState(1);
const { status, error, data } = useQuery<
CharacterResponse,
Error
>(["characters", { page }], getData);
...
}
页码被存储在状态中,并被初始化为第一页。然后,页码被传递到获取函数中。
我们把通常的状态变量从useQuery ,以便在获取过程的不同部分渲染不同的元素。
渲染列表
组件内部的渲染过程如下:
export function App() {
...
if (status === "loading") {
return <div>...</div>;
}
if (status === "error") {
return <div>{error!.message}</div>;
}
if (data === undefined) {
return null;
}
return (
<div>
<div>
<button
disabled={page <= 1}
onClick={() => setPage((p) => p - 1)}
>
Previous
</button>
<span>Page: {page}</span>
<button
disabled={!data.next}
onClick={() => setPage((p) => p + 1)}
>
Next
</button>
</div>
{
<div>
{data.results.map((d) => (
<div key={d.name}>{d.name}</div>
))}
</div>
}
</div>
);
}
当数据被加载时,上一页和下一页的分页按钮与数据页一起被渲染出来。
这个功能是正确的,但分页有点不正常:

keepPreviousData
在useQuery 钩子里有一个叫做keepPreviousData 的选项,它允许在新的请求的数据取代它之前保留之前的数据。我们可以添加这个选项,如下所示:
const { status, error, data } = useQuery<
CharacterResponse,
Error
>(["characters", { page }], getData, { \
keepPreviousData: true });
还有一个isPreviousData 的状态变量,可以被解构:
const {
status,
error,
data,
isPreviousData,} = useQuery<CharacterResponse, Error>(
["characters", { page }],
getData,
{ keepPreviousData: true }
);
这告诉我们渲染时是否会使用以前的数据。
我们现在可以对寻呼机按钮做如下调整:
<div>
<button
disabled={isPreviousData || page <= 1} onClick={() => setPage((p) => p - 1)}
>
Previous
</button>
<span>Page: {page}</span>
<button
disabled={isPreviousData || !data.next} onClick={() => setPage((old) => old + 1)}
>
Next
</button>
</div>
当数据被获取时,这将禁用这些按钮。
现在我们可以看到,分页变得更加顺畅了:

很好 😊
这篇文章中的代码可以在CodeSandbox中找到:https://codesandbox.io/s/react-query-paging-trxxk?file=/src/App.tsx