基于Next.js 13 Server Action实现加载更多分页的问题
实现方案
1. 解决随机不重复数据查询问题
Prisma针对不同数据库提供了随机排序方式,结合notIn过滤已加载ID即可避免重复:
- PostgreSQL环境:直接使用
orderBy: { _random: true }实现随机排序 - MySQL环境:通过
prisma.$queryRaw执行原生SQL,用ORDER BY RAND()实现随机 - 查询核心逻辑:每次请求传入已加载的ID数组,用
where: { id: { notIn: loadedIds } }排除已展示条目,再取对应数量的数据
示例Prisma查询代码:
// 假设数据模型为Item const getRandomItems = async (excludeIds: number[], take: number) => { return prisma.item.findMany({ where: { id: { notIn: excludeIds } }, orderBy: { _random: true }, // PostgreSQL适用,MySQL需替换为原生查询 take: take }); };
2. 用Server Action实现无刷新加载更多(解决页面回顶问题)
原代码用form默认提交会触发页面刷新,改用客户端组件+异步调用Server Action的方式,保持页面状态不重置:
步骤1:编写独立的Server Action
// app/actions.ts 'use server'; import prisma from '@/client'; export async function loadMoreItems(excludeIds: number[], take: number = 5) { try { return await prisma.item.findMany({ where: { id: { notIn: excludeIds } }, orderBy: { _random: true }, take: take }); } catch (error) { throw new Error('加载数据失败'); } }
步骤2:客户端组件管理加载状态
// app/LoadMoreButton.tsx 'use client'; import { useState, useCallback } from 'react'; import { loadMoreItems } from './actions'; interface Props { initialItems: { id: number; /* 其他数据字段 */ }[]; } export default function LoadMoreButton({ initialItems }: Props) { const [items, setItems] = useState(initialItems); const [isLoading, setIsLoading] = useState(false); const handleLoadMore = useCallback(async () => { setIsLoading(true); try { const excludeIds = items.map(item => item.id); const newItems = await loadMoreItems(excludeIds); setItems(prev => [...prev, ...newItems]); } catch (error) { console.error('加载失败:', error); } finally { setIsLoading(false); } }, [items]); return ( <> <div className="items-container"> {items.map(item => ( <div key={item.id}>{/* 渲染单条数据内容 */}</div> ))} </div> {items.length < 500 && ( <button onClick={handleLoadMore} disabled={isLoading} className="mt-4 px-4 py-2 bg-blue-500 text-white rounded" > {isLoading ? '加载中...' : '加载更多'} </button> )} </> ); }
步骤3:服务器组件预渲染初始数据
// app/page.tsx import Link from 'next/link'; import LoadMoreButton from './LoadMoreButton'; import prisma from '@/client'; export default async function Page() { // 预渲染初始15条随机数据 const initialItems = await prisma.item.findMany({ orderBy: { _random: true }, take: 15 }); return ( <div className="md:container mx-auto text-sky-700 dark:text-white"> <div className="py-6 px-1"> <div className="container mx-auto"> <div className="flex text-sm gap-1 font-medium items-center md:gap-4"> <span> <Link href="../">AFC Home</Link> / PAGE </span> </div> </div> </div> <div className="md:px-24 py-8 text-center mt-2 p-2"> <h2 className="text-3xl font-semibold px-8 md:text-6xl">Header</h2> <p className="py-6 font-medium md:text-xl md:my-10">TEXT</p> <LoadMoreButton initialItems={initialItems} /> </div> </div> ); }
3. 原代码回顶问题说明
原代码通过form提交触发Server Action,默认会触发页面完整刷新,因此会回到顶部。改用客户端组件异步调用Server Action的方式,页面不会刷新,自然保留滚动位置。
4. 关键注意事项
- 禁止用全局变量
pageId:服务器端全局变量是多用户共享的,会导致数据混乱,必须用客户端状态管理已加载的ID列表 - 数据边界处理:当加载数据总数达到500+时,隐藏「加载更多」按钮
- 错误提示:可添加加载失败的UI提示,提升用户体验
内容的提问来源于stack exchange,提问作者Radium Chris
相关产品推荐
相关产品推荐

