Next.js 13 Beta(appDir)环境下如何实现分页功能?
在Next.js 13 App Router中实现产品列表分页(含页码高亮)
核心思路
App Router模式下不再支持getStaticProps/getServerSideProps,改用Server Component获取初始分页数据,搭配Client Component处理路由跳转与交互,同时依赖API返回分页元信息(当前页、总页数、是否有下一页)来实现完整分页逻辑。
步骤1:Server Component获取分页数据(app/products/page.tsx)
默认情况下page.tsx是Server Component,可直接在组件内异步请求API,通过searchParams获取当前页码:
import Pagination from './Pagination'; // 定义类型,根据你的API返回结构调整 type Product = { id: number; name: string; price: number; }; type PaginationMeta = { currentPage: number; totalPages: number; hasNextPage: boolean; }; type ApiResponse = { products: Product[]; pagination: PaginationMeta; }; export default async function ProductsPage({ searchParams, }: { searchParams: { page?: string }; }) { // 解析当前页码,默认显示第1页 const currentPage = parseInt(searchParams.page) || 1; // 请求分页数据,可选添加缓存策略 const res = await fetch( `http://127.0.0.1:8000/store/products/?page=${currentPage}`, { next: { revalidate: 60 } } // 60秒后重新验证缓存 ); if (!res.ok) throw new Error('获取产品列表失败'); const { products, pagination } = await res.json() as ApiResponse; return ( <div className="container mx-auto p-4"> <h1 className="text-2xl font-bold mb-4">产品列表</h1> {/* 产品列表展示 */} <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8"> {products.map(product => ( <div key={product.id} className="border p-4 rounded-lg"> <h3 className="font-semibold">{product.name}</h3> <p className="text-gray-600">${product.price.toFixed(2)}</p> </div> ))} </div> {/* 分页组件 */} <Pagination currentPage={pagination.currentPage} totalPages={pagination.totalPages} hasNextPage={pagination.hasNextPage} /> </div> ); }
步骤2:Client Component实现分页交互(app/products/Pagination.tsx)
添加'use client'指令转为客户端组件,处理路由跳转、页码高亮和下一页逻辑:
'use client'; import { useRouter, useSearchParams } from 'next/navigation'; type PaginationProps = { currentPage: number; totalPages: number; hasNextPage: boolean; }; export default function Pagination({ currentPage, totalPages, hasNextPage, }: PaginationProps) { const router = useRouter(); const searchParams = useSearchParams(); // 生成优化后的页码列表(避免显示过多页码) const generatePageNumbers = () => { const pages: (number | string)[] = []; const start = Math.max(1, currentPage - 2); const end = Math.min(totalPages, currentPage + 2); // 添加首页和省略号(如果需要) if (start > 1) pages.push(1); if (start > 2) pages.push('...'); // 添加当前页前后的页码 for (let i = start; i <= end; i++) pages.push(i); // 添加末页和省略号(如果需要) if (end < totalPages - 1) pages.push('...'); if (end < totalPages) pages.push(totalPages); return pages; }; // 切换页码逻辑 const handlePageChange = (page: number) => { const newParams = new URLSearchParams(searchParams); newParams.set('page', page.toString()); router.push(`/products?${newParams.toString()}`); }; const pageNumbers = generatePageNumbers(); return ( <div className="flex items-center justify-center gap-2"> {/* 上一页按钮 */} <button onClick={() => handlePageChange(currentPage - 1)} disabled={currentPage === 1} className="px-4 py-2 border rounded disabled:opacity-50 disabled:cursor-not-allowed" > 上一页 </button> {/* 页码列表(含高亮) */} {pageNumbers.map((page, idx) => ( <span key={idx} className={`px-3 py-2 rounded cursor-pointer ${ typeof page === 'number' && page === currentPage ? 'bg-blue-500 text-white' : 'border hover:bg-gray-100' }`} onClick={() => typeof page === 'number' && handlePageChange(page)} > {page} </span> ))} {/* 下一页按钮 */} <button onClick={() => handlePageChange(currentPage + 1)} disabled={!hasNextPage} className="px-4 py-2 border rounded disabled:opacity-50 disabled:cursor-not-allowed" > 下一页 </button> </div> ); }
关键说明
- API要求:确保你的API返回分页元信息(当前页、总页数、是否有下一页),如果API只返回总条数,可通过
totalPages = Math.ceil(totalItems / itemsPerPage)计算总页数。 - 路由跳转:通过
useRouter和useSearchParams修改URL的page参数,实现无刷新分页。 - 页码高亮:通过判断当前页与点击页码是否匹配,给对应元素添加背景色高亮。
- 缓存优化:
fetch时添加next.revalidate可设置缓存过期时间,减少重复请求。 - Loading状态:可在
app/products目录下创建loading.tsx,添加全局加载提示。
内容的提问来源于stack exchange,提问作者Sabbir Hasan Munna
相关产品推荐
相关产品推荐

