Contents

如何使用 Next.js 和 TanStack 查詢實現無限滾動和分頁

您將開發的大多數應用程式都會管理資料;隨著計劃不斷擴大,其數量可能會越來越大。當應用程式無法有效管理大量資料時,它們的效能就會很差。

利用分頁和無限滾動是優化應用程式效率的實用方法,可以改善資料渲染的處理,同時增強整體使用者體驗。

使用 TanStack 查詢進行分頁和無限滾動

TanStack Query「React Query 的改編版」是一個用於 JavaScript 應用程式的強大的狀態管理函式庫。它提供了一種有效的解決方案,用於管理應用程式狀態以及其他功能,包括資料相關任務(例如快取)。

/bc/images/laptop-on-a-desk.jpg

分頁是一種組織大量資料集的方法,透過使用分頁控制項將資料集分割成更小的、易於導覽的部分。另一方面,無限滾動提供了一種自適應瀏覽方法,當用戶向下滾動時,附加資訊被無縫加載和顯示,從而消除了直接導航的需要。

分頁和無限滾動都是以用戶友好的方式管理和顯示大量資訊的方法,根據應用程式的特定需求,每種方法都有自己的優點和缺點。

您可以在為其指定的 GitHub 儲存庫中找到該專案的原始程式碼。

設定 Next.js 項目

要啟動該過程,請透過安裝其最新迭代版本 13 來建立 Next.js 項目,該項目使用「App」目錄作為其基礎。

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

若要繼續,您需要使用 npm(廣泛使用的 Node.js 應用程式套件管理器)在專案中安裝 TanStack 套件。

 npm i @tanstack/react-query 

在 Next.js 應用程式中整合 TanStack 查詢

要將 TanStack Query 合併到 Next.js 專案中,需要在應用程式的核心(特別是在layout.js 檔案中)建立並初始化 TanStack Query 的新實例。這可以透過從 TanStack Query 導入 QueryClient 和 QueryClientProvider 來實現。隨後,用 QueryClientProvider 包圍子屬性,結構如下:

 "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 瞭解和操作軟體目前狀態的完全權限。

使用 useQuery Hook 實作分頁

使用「useQuery」鉤子透過合併分頁屬性(包括頁碼)來促進資料的高效檢索和處理,以無縫獲取資訊的目標部分。

Advanced Repeat 具有高度通用性,並提供一系列選項,可根據您的特定要求自訂資料檢索流程。這包括輕鬆建立快取設定和管理載入條件的能力,從而產生平滑且統一的分頁結果。

為了將分頁合併到我們的 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 取得資料的函數。在這種情況下,請使用 JSONPlaceholder 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;
    }
  };

  // add the following code here
} 

可以透過指定具有多個鍵值對的物件來定義 useQuery 鉤子,這些鍵值對錶示從 GraphQL API 取得資料所需的各種參數。這些參數可能包括查詢字串本身、用於自訂查詢的變數、用於控制資料取得和更新方式的選項,以及出現網路錯誤或其他問題時的重試策略。透過在 useQuery 鉤子的上下文中提供這些參數,我們使 React 元件樹能夠管理與查詢生命週期相關的狀態,從而實現更有效率的更新和更流暢的使用者體驗。

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

“keepPreviousData”屬性設定為“true”,從而在獲取新資訊時能夠保留先前的資料。 「queryKey」變數包含一個鍵列表,其中包括所需的端點和所需的頁碼。最後,呼叫“fetchPosts”函數作為資料採集的觸發器。

在先前的討論中,我們談到了這樣一個事實:鉤子提供了多種狀態,可以以類似於陣列和物件的剖析的方式解構這些狀態。透過採用這些狀態,可以透過在資料檢索階段呈現合適的介面來增強整體使用者體驗。可用選項包括“isLoading”、“isError”和其他各種選項。

為了根據正在進行的過程的當前狀態顯示各種訊息螢幕,有必要合併所提供的程式碼片段,這使得能夠為正在進行的過程的每個階段呈現獨特的訊息顯示。

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

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

總之,提供將在 Web 瀏覽器中顯示的 JavaScript 元件的程式碼至關重要。這不僅可以確保元件的正確呈現,而且還可以透過使用事件處理程序和資料獲取方法來實現客戶端和伺服器端應用程式之間的通訊。

利用 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」以取得進一步說明。

/bc/images/tanstack-query-pagination-example-in-next-js-application.jpg

將分頁元件合併到應用程式的目錄結構中會提示 Next.js 將其識別為指定路線,從而可以透過其特定 URL 無縫導航到相應的網頁。

使用 useInfiniteQuery 鉤子進行無限滾動

無限滾動透過在用戶繼續滾動時動態加載附加內容來創建看似不間斷的用戶介面。這種情況的一個示例性實例可以在 YouTube 的功能中找到,其中,當人們瀏覽網頁時,可以毫不費力地檢索和呈現新的影片條目,而不會出現任何可觀察到的中斷。

「useInfiniteQuery」鉤子的使用使得人們能夠透過從遠端伺服器檢索資料來促進無限滾動,其中隨著使用者逐漸下降,自動請求並呈現後續頁面。

為了將無限滾動功能合併到您的應用程式中,有必要建立一個名為「InfiniteScroll/page.js」的新 JavaScript 檔案並將其放置在「src/app」目錄中。隨後,您將需要匯入各種模組,包括 React 元件和狀態管理庫,這有助於將此功能與現有程式碼庫無縫整合。

 "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;
    }
  };

  // add the following code here
} 

與分頁實作相比,分頁實作不會在獲取資料時引入任何延遲,此特定程式碼在獲取新資訊之前有意暫停約兩秒。這種有目的的延遲旨在讓用戶有足夠的時間滾動和探索當前資料集,最終在他們繼續瀏覽體驗時自動檢索更新的資料集。

初始化後,「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 變數可作為累加器,用於收集各個頁面的所有貼文並將它們平鋪到一個統一的陣列中。透過這樣做,它簡化了單獨迭代和渲染每個帖子的過程。

人們可以實作一項功能,透過定義一個函數,使用 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]); 

總之,合併將在 Web 瀏覽器中顯示的 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 的應用程式的操作效率和使用者滿意度。