在 【分頁 (Pagination) 是什麼?前端與後端分頁實現方法與優劣分析】這篇文章中,簡單介紹了分頁是什麼,也包括了分頁的目的以及前端分頁與後端分頁的區別,本篇文章則會接著介紹我如何在 React 專案中實現前端分頁。
本篇文章專注於前端分頁的實作細節,關於為何選擇 Client-side Pagination ,或是你還不知道分頁是什麼,歡迎先閱讀上一篇文章!對接下來內容有興趣的讀者歡迎繼續往下閱讀~~
專案背景
我目前開發的是一個 React + Vite 的部落格文章網站。在這個網站中也會同步分享許多程式設計相關的文章。因為文章數會在數十篇左右,考慮這個規模下我沒有建立資料庫,而是透過靜態網站存取資料,也因為是靜態網站,採取的分頁技術是前端分頁 (Client-side Pagination)。
組件架構
在進入實作細節前,先確認這個分頁設計的兩個組件與個別的職責:
父元件:Article.jsx
這個元件用來管理所有文章,原本的設計就是用 map 將所有的文章條列出來。現在要引入分頁技術,因此需要改寫這邊的檔案。
父元件需要控制資料的處理邏輯:
- 狀態管理:持有原始文章列表、搜尋字串、以及當前的頁碼 (
currentPage)。 - 資料加工:執行搜尋過濾與分頁切片運算。
- 分發指令:將計算好的分頁資訊(如當前頁數、總頁數)傳遞給子組件,並定義頁碼切換行為。
子元件:Pagination.jsx
這個元件就非常單純,只用於畫出我們想要的分頁按鈕 UI,並實現點及按鈕時跳轉頁面。
- 渲染邏輯:根據父元件傳入的總頁數,計算何時該顯示數字、何時該顯示省略號。
- 觸發事件:當使用者點擊頁碼時,呼叫父元件傳入的 callback function 來改變
currentPage。
實作步驟
1. 資料切片 (Array Slicing):
在實現分頁前須要先確認幾個項目
- 每個分頁要呈現的資料數量
- 分頁數量
- 該分頁要呈現的是哪些資料
- 分頁按鈕顯示數量
const start = (currentPage - 1) * ITEMS_PER_PAGE;
return filteredArticles.slice(start, start + ITEMS_PER_PAGE);
// 1. 設定初始值與預設值
const [currentPage, setCurrentPage] = useState(1);
const ITEMS_PER_PAGE = 4;
const MAX_VISIBLE_PAGE = 3;
// 2. 計算總頁數
const totalPages = Math.ceil(filterArticles.length/ ITEMS_PER_PAGE);
// 3. 取得當前頁面要呈現的資料切片
const currentArticles = useMemo(()=>{
const start = (currentPage - 1 ) * ITEMS_PER_PAGE;
return filterArticles.slice(start, start + ITEMS_PER_PAGE);
},[filterArticles, currentPage]);
// 4. 處理換頁用的函式
const handlePageChange = (page)=>{
setCurrentPage(page);
window.scrollTo({top: 0, behavior: "smooth"});
};
// 5. 將資料傳入 pagination 元件
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
maxVisiblePsges={MAX_VISIBLE_PAGE}
/>
- 透過 React 的 useState 記住目前是第幾頁 (currentPage),當前頁碼的初始值為 1、每頁呈現 4 篇文章、頁碼按鈕最多呈現 3 個。
- 總頁數用「總文章數/每頁文章數」,用無條件進位,避免不能整除的數字造成資料遺漏。
- 使用 JavaScript 原生的 .slice() 陣列切割語法,算好每頁的頭跟尾
- 處理換頁的函式呼叫
setCurrentPage切換currentPage,當currentPage改變時會觸發currentArticles重新計算要陳列的資料。
2. 動態分頁元件 (<Pagination />):
用一個獨立的元件來畫出底部的頁碼。這個元件內部包含了一個省略號邏輯,如果總頁數非常多,元件會自動隱藏中間的數字並顯示省略號 (例如:1, 2, 3 ... 10),保持畫面整潔,而不會是一整排十幾個按鈕。
顯示頁碼前,需要先制定一下顯示的策略:
- 最多顯示 n 個連續數字按鈕
- 假設總頁數 <= n,一次顯示所有頁面按鈕 (例:n = 3,頁面只有3頁,一次顯示:1, 2, 3)
- 保留頭尾的數字按鈕,以及「以當前頁面為中心」的 n 個頁面按鈕 (例:1, ... 5, 6, 7, ... 12)
主要處理顯示邏輯的程式碼:
const getVisiblePages = ()=>{
// 總頁數小於顯示頁數,直接返回頁數陣列
if(totalPages <= maxVisiblePages){
return Array.from({length: totalPages}, (_,i)=> i+1);
}
// 計算要顯示的連續數字
const halfVisible = Math.floor(maxVisiblePages/2);
let startPage = Math.max(currentPage-halfVisible , 1);
let endPage = Math.min(startPage + maxVisiblePages-1 , totalPages);
// 處理邊界的狀況,如果剩下的顯示頁數少於最大可顯示頁數,將起始往左移
if(endPage - startPage + 1 <maxVisiblePages){
startPage = Math.max(endPage - maxVisiblePages + 1, 1);
}
const pages = [];
// 處理起始部分
if(startPage >1){
pages.push(1);
if(startPage > 2) pages.push("...");
}
// 放入連續頁碼
for (let i = startPage; i<= endPage; i++){
pages.push(i);
}
// 處理結尾部分
if(endPage < totalPages){
if(endPage < totalPages -1) pages.push("...");
pages.push(totalPages);
}
return pages;
}
顯示邏輯:
const visiblePages = getVisiblePages();
return(
<div className="btn-number-container">
{visiblePages.map((page, index)=>{
if(page === "..."){
return( {`...`} )
}
return(
<button
key={`${page}-${index}`}
onClick={()=>onPageChange(page)}
>
{page}
</button>
)
})}
</div>
)
總結
以上就是我的前端分頁實作紀錄~ 透過將資料處理邏輯放在父元件,並將 UI 獨立封裝為子元件,在開發方面,不僅讓程式碼更易於維護,也實現了元件的可重用性。
雖然前端分頁看似簡單,但要做到 UX 友善與邏輯嚴謹仍需注意許多細節,像是我做的第一版並沒有加入省略符號的設計,這樣會讓文章數量變多時使用體驗很差~ 不過寫程式就是這樣,完成後總會發現可以再優化的地方,這也正是程式開發的樂趣所在~












