如何使用 Next.js 和 TanStack 查询实现无限滚动和分页

您将开发的大多数应用程序都会管理数据; 随着计划不断扩大,其数量可能会越来越大。 当应用程序无法有效管理大量数据时,它们的性能就会很差。

分页和无限滚动是可用于优化应用程序性能的两种流行技术。 它们可以帮助您更有效地处理数据渲染并增强整体用户体验。

使用 TanStack 查询进行分页和无限滚动

TanStack查询——React Query 的改编版——是一个用于 JavaScript 应用程序的强大的状态管理库。 它提供了一种有效的解决方案,用于管理应用程序状态以及其他功能,包括数据相关任务(例如缓存)。

分页涉及将大型数据集划分为较小的页面,允许用户使用导航按钮以可管理的块形式导航内容。 相比之下,无限滚动提供了更加动态的浏览体验。 当用户滚动时,新数据会自动加载并显示,从而无需显式导航。

分页和无限滚动旨在有效管理和呈现大量数据。 两者之间的选择取决于应用程序的数据要求。

您可以在此处找到该项目的代码 GitHub 存储库。

设置 Next.js 项目

首先,创建一个 Next.js 项目。 安装使用 App 目录的最新版本的 Next.js 13。

 npx create-next-app@latest next-project --app 

接下来,使用 Node 包管理器 npm 在项目中安装 TanStack 包。

 npm i @tanstack/react-query 

将 TanStack 查询集成到 Next.js 应用程序中

要将 TanStack Query 集成到 Next.js 项目中,您需要在应用程序的根目录(layout.js 文件)中创建并初始化 TanStack Query 的新实例。 为此,请从 TanStack Query 导入 QueryClient 和 QueryClientProvider。 然后,使用 QueryClientProvider 包装 child 的 prop,如下所示:

 "use client"
import React from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({ children }) {
  const queryClient = new QueryClient();

  return (
    <html lang="en">
      <body>
        <QueryClientProvider client={queryClient}>
          {children}
        </QueryClientProvider>
      </body>
    </html>
  );
}

export { metadata };

此设置可确保 TanStack Query 能够完全访问应用程序的状态。

  修复您的麦克风被 Google Meet 中的系统设置静音

useQuery 挂钩简化了数据获取和管理。 通过提供分页参数(例如页码),您可以轻松检索特定的数据子集。

此外,该挂钩提供了各种选项和配置来自定义数据获取功能,包括设置缓存选项以及有效处理加载状态。 借助这些功能,您可以轻松创建无缝的分页体验。

现在,要在 Next.js 应用程序中实现分页,请在 src/app 目录中创建一个 Pagination/page.js 文件。 在此文件中,进行以下导入:

 "use client"
import React, { useState } from 'react';
import { useQuery} from '@tanstack/react-query';
import './page.styles.css';

然后,定义一个 React 功能组件。 在此组件内,您需要定义一个从外部 API 获取数据的函数。 在这种情况下,请使用 JSON占位符 API 获取一组帖子。

 export default function Pagination() {
  const [page, setPage] = useState(1);

  const fetchPosts = async () => {
    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/posts?
                                  _page=${page}&_limit=10`);

      if (!response.ok) {
        throw new Error('Failed to fetch posts');
      }

      const data = await response.json();
      return data;
    } catch (error) {
      console.error(error);
      throw error;
    }
  };

  
}

现在,定义 useQuery 挂钩,并将以下参数指定为对象:

   const { isLoading, isError, error, data } = useQuery({
    keepPreviousData: true,
    queryKey: ['posts', page],
    queryFn: fetchPosts,
  });

keepPreviousData 值为 true,这可确保应用程序在获取新数据时保留以前的数据。 queryKey 参数是一个包含查询键的数组,在本例中为端点和要获取数据的当前页面。 最后,queryFn 参数 fetchPosts 触发函数调用以获取数据。

如前所述,该钩子提供了几种可以解压的状态,类似于解构数组和对象的方式,并利用它们在数据获取过程中改善用户体验(呈现适当的 UI)。 这些状态包括 isLoading、isError 等等。

为此,请包含以下代码以根据正在进行的进程的当前状态呈现不同的消息屏幕:

   if (isLoading) {
    return (<h2>Loading...</h2>);
  }

  if (isError) {
    return (<h2 className="error-message">{error.message}</h2>);
  }

最后,包含将在浏览器页面上呈现的 JSX 元素的代码。 该代码还具有另外两个功能:

  • 一旦应用程序从 API 获取帖子,它们将存储在 useQuery 挂钩提供的数据变量中。 该变量有助于管理应用程序的状态。 然后,您可以映射存储在该变量中的帖子列表,并将它们呈现在浏览器上。
  • 添加两个导航按钮“上一个”和“下一个”,以允许用户相应地查询和显示其他分页数据。
   return (
    <div>
      <h2 className="header">Next.js Pagination</h2>
      {data && (
        <div className="card">
          <ul className="post-list">
            {data.map((post) => (
                <li key={post.id} className="post-item">{post.title}</li>
            ))}
          </ul>
        </div>
      )}
      <div className="btn-container">
        <button
          onClick={() => setPage(prevState => Math.max(prevState - 1, 0))}
          disabled={page === 1}
          className="prev-button"
        >Prev Page</button>

        <button
          onClick={() => setPage(prevState => prevState + 1)}
          className="next-button"
        >Next Page</button>
      </div>
    </div>
  );

最后,启动开发服务器。

 npm run dev 

然后,在浏览器中访问 http://localhost:3000/Pagination。

  如何了解你在历史上不同时代的样子

由于您在应用程序目录中包含了 Pagination 文件夹,Next.js 将其视为路由,允许您访问该 URL 处的页面。

无限滚动提供无缝的浏览体验。 YouTube 就是一个很好的例子,它会自动获取新视频并在您向下滚动时显示它们。

useInfiniteQuery 钩子允许您通过从服务器获取页面中的数据并在用户向下滚动时自动获取和渲染下一页数据来实现无限滚动。

要实现无限滚动,请在 src/app 目录中添加 InfiniteScroll/page.js 文件。 然后,进行以下导入:

 "use client"
import React, { useRef, useEffect, useState } from 'react';
import { useInfiniteQuery } from '@tanstack/react-query';
import './page.styles.css';

接下来,创建一个 React 功能组件。 在此组件内,与分页实现类似,创建一个用于获取帖子数据的函数。

 export default function InfiniteScroll() {
  const listRef = useRef(null);
  const [isLoadingMore, setIsLoadingMore] = useState(false);

  const fetchPosts = async ({ pageParam = 1 }) => {
    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/posts?
                                  _page=${pageParam}&_limit=5`);

      if (!response.ok) {
        throw new Error('Failed to fetch posts');
      }

      const data = await response.json();
      await new Promise((resolve) => setTimeout(resolve, 2000));
      return data;
    } catch (error) {
      console.error(error);
      throw error;
    }
  };

  
}

与分页实现不同,此代码在获取数据时引入了两秒的延迟,以允许用户在滚动以触发重新获取一组新数据时探索当前数据。

现在,定义 useInfiniteQuery 挂钩。 当组件最初安装时,挂钩将从服务器获取第一页数据。 当用户向下滚动时,挂钩将自动获取下一页数据并将其呈现在组件中。

   const { data, fetchNextPage, hasNextPage, isFetching } = useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    getNextPageParam: (lastPage, allPages) => {
      if (lastPage.length < 5) {
        return undefined;
      }
      return allPages.length + 1;
    },
  });

  const posts = data ? data.pages.flatMap((page) => page) : [];

posts 变量将不同页面的所有帖子合并到一个数组中,从而产生 data 变量的扁平版本。 这使您可以轻松映射和渲染各个帖子。

  如何在 2022 年创建动画 SVG

要跟踪用户滚动并在用户接近列表底部时加载更多数据,您可以定义一个函数,利用 Intersection Observer API 来检测元素何时与视口相交。

   const handleIntersection = (entries) => {
    if (entries[0].isIntersecting && hasNextPage && !isFetching && !isLoadingMore) {
      setIsLoadingMore(true);
      fetchNextPage();
    }
  };

  useEffect(() => {
    const observer = new IntersectionObserver(handleIntersection, { threshold: 0.1 });

    if (listRef.current) {
      observer.observe(listRef.current);
    }

    return () => {
      if (listRef.current) {
        observer.unobserve(listRef.current);
      }
    };
  }, [listRef, handleIntersection]);

  useEffect(() => {
    if (!isFetching) {
      setIsLoadingMore(false);
    }
  }, [isFetching]);

最后,包含在浏览器中呈现的帖子的 JSX 元素。

   return (
    <div>
      <h2 className="header">Infinite Scroll</h2>
      <ul ref={listRef} className="post-list">
        {posts.map((post) => (
          <li key={post.id} className="post-item">
            {post.title}
          </li>
        ))}
      </ul>
      <div className="loading-indicator">
        {isFetching ? 'Fetching...' : isLoadingMore ? 'Loading more...' : null}
      </div>
    </div>
  );

完成所有更改后,请访问 http://localhost:3000/InfiniteScroll 查看它们的实际效果。

TanStack 查询:不仅仅是数据获取

分页和无限滚动是突出 TanStack Query 功能的好例子。 简单来说,它是一个全能的数据管理库。

凭借其广泛的功能,您可以简化应用程序的数据管理流程,包括有效的状态处理。 除了其他与数据相关的任务外,您还可以提高 Web 应用程序的整体性能以及用户体验。