You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 20:58:15