使用 Next.js、MUI 和 react-query 实现分页

2025-06-05

使用 Next.js、MUI 和 react-query 实现分页

如果您需要获取、缓存页面上的数据,并进行精美的分页,以获得出色的用户体验,那么您来对地方了。几天前,我在工作中实现了一个解决这个问题的解决方案,想与大家分享:

1. 设置项目

我不想用冗长的篇幅来烦你设置和创建样板,所以我假设你已经熟悉了基础知识。如果你还有疑问,也可以在这个仓库里查看已经完成的项目。开始吧:

  • 您需要一个全新的 Next.js 项目,并安装 React Query 和 Material-ui。我选择了 Material-ui v4,因为我们工作中使用的就是这个版本,但您也可以使用其他版本,只需记住 import 语句和用法可能略有不同。
  • 首先,你需要从 Rick and Morty API 获取一些需要分页的数据。我们不会使用 useEffect hook 来获取数据,而是使用 react-query。为了使其正常工作,你需要在 _app.js 文件中配置一个提供程序:
import "../styles/globals.css";
import { ReactQueryDevtools } from "react-query/devtools";

import { QueryClient, QueryClientProvider } from "react-query";

const queryClient = new QueryClient();

function MyApp({ Component, pageProps }) {
  return (
    <QueryClientProvider client={queryClient}>
      <Component {...pageProps} />
      <ReactQueryDevtools initialIsOpen={false}></ReactQueryDevtools>
    </QueryClientProvider>
  );
}

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

这是纯粹从react-query 文档中获取的设置:我们配置了一个不带选项的 queryClient,并将我们的应用程序包装在 QueryClientProvider 中。此外,我还添加了 ReactQueryDevtools,以便更轻松地查看数据以及缓存的工作原理。

2. 使用 react-query 获取并显示数据

  • 现在,在 index.js 页面或您选择的任何其他页面中,导入 useQuery 钩子。它接受两个参数:第一个参数是一个字符串,用作查询的名称;第二个参数是用于获取数据的函数。为了能够在页面上看到一些内容,我将字符串化的数据打印在 div 标签内。
import { useQuery } from "react-query";

export default function PaginationPage(props) {
  const { data } = useQuery(
    "characters",
    async () =>
      await fetch(`https://rickandmortyapi.com/api/character/`).then((result) =>
        result.json()
      )
  );
  console.log(data);
  return <div>{JSON.stringify(data)}</div>;
}
Enter fullscreen mode Exit fullscreen mode

React 查询设置正确

结果应该与上图类似。请记住,您仍在异步获取数据,因此正如您在控制台中看到的,在开始时会有一段时间数据对象未定义。此外,如果您点击左上角的花朵图标,则会打开 React-Query 开发者工具。在那里,您可以看到刚刚执行的查询,当您点击它时,它甚至会让您看到获取的查询数据,因此您实际上不需要我编写的 console.log。

  • 现在我们的应用程序中已经有了一些数据,让我们快速设置一些看起来不错的东西来显示我们刚刚获取的 Rick 和 Morty 角色:
<h1>Rick and Morty with React Query and Pagination</h1>
      <div className='grid-container'>
        {data?.results?.map((character) => (
          <article key={character.id}>
            <img
              src={character.image}
              alt={character.name}
              height={200}
              loading='lazy'
              width={200}
            />
            <div className='text'>
              <p>Name: {character.name}</p>
              <p>Lives in: {character.location.name}</p>
              <p>Species: {character.species}</p>
              <i>Id: {character.id} </i>
            </div>
          </article>
        ))}
      </div>
Enter fullscreen mode Exit fullscreen mode

这里没什么特别的:如果有数据,我们就迭代它,然后显示一张图片和一些关于角色的数据。 样式如下,我只是把它们写在了 globals.css 文件中。虽然看起来不太酷,但确实能用。
无分页的角色卡

.grid-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 2rem;
  max-width: 1300px;
  width: 100%;
  margin: auto;
  padding: 2rem;
}

article {
  padding: 1em;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  border-radius: 0.5em;
  box-shadow: rgba(99, 99, 99, 0.5) 0px 2px 8px 0px;
}
Enter fullscreen mode Exit fullscreen mode

到目前为止,我们的应用程序无法显示 API 默认返回的前 20 个项目之外的数据,所以让我们改变这一点。

3. 使用 Material UI 添加分页

  • 导入 Material UI 分页组件并将其放置在网格容器上方。 count 属性控制显示的页面数量,我们已经从 API 中获取了此信息。
import Pagination from "@material-ui/lab/Pagination";
...
return (
<div>
  <h1>Rick and Morty with React Query and Pagination</h1>
      <Pagination
        count={data?.info.pages}
        variant='outlined'
        color='primary'
        className='pagination'
      />
      <div className='grid-container'>
...
Enter fullscreen mode Exit fullscreen mode

分页组件

  • 然后,设置一些状态来保存当前页面,并将页面参数添加到 API 调用中。这也意味着我们可以将当前页面传递给 MUI 分页组件,以便它知道要突出显示哪个数字。
import { useState } from "react";
...
const [page, setPage] = useState(1);
const { data } = useQuery(
    "characters",
    async () =>
      await fetch(
        `https://rickandmortyapi.com/api/character/?page=${page}`
      ).then((result) => result.json())
  );
  return (
    <div>
      <h1>Rick and Morty with React Query and Pagination</h1>
      <Pagination
        count={data?.info.pages}
        variant='outlined'
        color='primary'
        className='pagination'
        page={page}
      />
...
Enter fullscreen mode Exit fullscreen mode
  • 最后一步,我们需要为 Pagination 组件定义 onChange 处理程序。该处理程序会更新页面状态,并对 URL 进行浅层推送。为了让 react-query 获取新数据,我们必须将 page 变量添加到查询键。我们将传入一个数组,其中包含字符串以及所有我们想要触发新 API 调用的变量,而不是字符串“characters”。
import { useRouter } from "next/router";
...
const router = useRouter();
const { data } = useQuery(
    ["characters", page],
    async () =>
      await fetch(
        `https://rickandmortyapi.com/api/character/?page=${page}`
      ).then((result) => result.json())
  );
function handlePaginationChange(e, value) {
    setPage(value);
    router.push(`pagination/?page=${value}`, undefined, { shallow: true });
  }
  return (
    <div>
      <h1>Rick and Morty with React Query and Pagination</h1>
      <Pagination
        count={data?.info.pages}
        variant='outlined'
        color='primary'
        className='pagination'
        page={page}
        onChange={handlePaginationChange}
      />
Enter fullscreen mode Exit fullscreen mode

现在,分页功能已经完美运行!点击不同的页面,虽然你已经看过《瑞克和莫蒂》的所有季了,但你还是会被那些你不认识的角色弄得眼花缭乱……

4. 外观改进

这里有两个小问题无法正常工作:第一个问题是,当用户直接访问 URL 时my-domain.com/pagination?page=5,我们的应用程序不会显示第 5 页的结果,因为我们从未在页面加载时读取查询参数。我们可以使用 useEffect hook 来解​​决这个问题,该 hook 从 Next.js 路由器对象读取 queryParam,并且仅在所有组件首次挂载时运行:

useEffect(() => {
    if (router.query.page) {
      setPage(parseInt(router.query.page));
    }
  }, [router.query.page]);
Enter fullscreen mode Exit fullscreen mode

另一方面,当你点击页面时,你会看到 Pagination 组件闪烁:每次获取数据时,它都会获取页面长度信息,但由于数据未定义,获取过程中页面会缩小到只显示一页。我们可以通过在 useQuery 钩子上设置一个配置对象来避免这种情况:

const { data } = useQuery(
    ["characters", page],
    async () =>
      await fetch(
        `https://rickandmortyapi.com/api/character/?page=${page}`
      ).then((result) => result.json()),
    {
      keepPreviousData: true,
    }
  );
Enter fullscreen mode Exit fullscreen mode

keepPreviousData 指令将在获取数据对象时保留先前的数据,并在已经有新数据时替换它,从而避免数据暂时未定义的情况。

希望这些对你有帮助!如果你能成功,或者有什么反馈,请告诉我。
现在,恕我直言,我得去看《瑞克和莫蒂》了,因为剧里的角色都让我很想重温一遍。

文章来源:https://dev.to/frontenddeveli/implementing-pagination-with-nextjs-mui-and-react-query-2ab
PREV
要学习的重要 CSS 概念。
NEXT
Hyperscript——React 的隐藏语言