如何实现博客文章分批加载?解决数组分段与重复加载问题
实现博客分批加载(避免重复加载)
要实现首次加载6篇、点击加载更多再加载后续6篇的需求,核心是让文章获取逻辑支持按范围读取,而非每次从开头重复取,并在客户端维护已加载的文章列表。以下是具体实现步骤:
1. 修改文章读取函数,支持范围查询
原getBlogPosts每次从第0篇开始取前n篇,导致重复加载。我们修改它接收offset(起始索引)和limit(读取数量)参数,只读取指定范围的文章,同时返回是否还有更多文章:
import fs from 'fs'; import matter from 'gray-matter'; export async function getBlogPosts(offset: number = 0, limit: number = 6) { // 读取posts文件夹下所有文件,先按日期排序(假设文件名含日期,如2024-05-01-post.md) const files = fs.readdirSync('posts').sort((a, b) => { const dateA = a.split('-').slice(0, 3).join('-'); const dateB = b.split('-').slice(0, 3).join('-'); return new Date(dateB).getTime() - new Date(dateA).getTime(); }); // 读取指定范围的文件 const targetFiles = files.slice(offset, offset + limit); const posts = targetFiles.map(fileName => { const slug = fileName.replace('.md', ''); const content = fs.readFileSync(`posts/${fileName}`, 'utf-8'); const { data: frontmatter } = matter(content); return { slug, frontmatter }; }); // 判断是否还有更多文章可加载 const hasMore = offset + limit < files.length; return { posts, hasMore }; }
2. 创建API路由供客户端调用
因为客户端无法直接使用Node.js的fs模块,所以需要创建一个API路由,让客户端通过HTTP请求获取后续文章:
// app/api/posts/route.ts import { NextResponse } from 'next/server'; import { getBlogPosts } from '@/lib/getBlogPosts'; // 假设函数放在lib目录下 export async function GET(request: Request) { const { searchParams } = new URL(request.url); const offset = parseInt(searchParams.get('offset') || '0'); const limit = parseInt(searchParams.get('limit') || '6'); const { posts, hasMore } = await getBlogPosts(offset, limit); return NextResponse.json({ posts, hasMore }); }
3. 改造博客页面,实现客户端加载更多
将页面拆分为服务器端预加载初始文章和客户端处理加载更多逻辑两部分:
服务器端页面(预加载初始6篇)
// app/blogs/page.tsx import { getBlogPosts } from '@/lib/getBlogPosts'; import BlogsClient from './BlogsClient'; export default async function BlogsPage() { // 服务器端预加载前6篇文章 const { posts, hasMore } = await getBlogPosts(0, 6); return <BlogsClient initialPosts={posts} initialHasMore={hasMore} />; }
客户端组件(处理加载更多)
// app/blogs/BlogsClient.tsx 'use client'; import { useState } from 'react'; import Link from 'next/link'; type Post = { slug: string; frontmatter: { title: string }; }; export default function BlogsClient({ initialPosts, initialHasMore }: { initialPosts: Post[], initialHasMore: boolean }) { const [posts, setPosts] = useState<Post[]>(initialPosts); const [hasMore, setHasMore] = useState(initialHasMore); const [isLoading, setIsLoading] = useState(false); let currentOffset = 6; // 初始已加载6篇,下一批从索引6开始 const loadMore = async () => { if (!hasMore || isLoading) return; setIsLoading(true); try { const res = await fetch(`/api/posts?offset=${currentOffset}&limit=6`); const data = await res.json(); // 合并新文章到现有列表,避免重复 setPosts(prev => [...prev, ...data.posts]); setHasMore(data.hasMore); currentOffset += 6; } catch (err) { console.error('加载更多失败:', err); } finally { setIsLoading(false); } }; return ( <div className="mx-auto flex flex-wrap"> {posts.map(({ slug, frontmatter }) => ( <div key={slug} className="m-2 flex flex-col overflow-hidden rounded-xl border border-gray-200 shadow-lg w-[300px]" > <Link href={`/blog/${slug}`}> <h3 className="p-4">{frontmatter.title}</h3> </Link> </div> ))} {hasMore && ( <button onClick={loadMore} disabled={isLoading} className="mx-auto mt-8 px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors disabled:opacity-70" > {isLoading ? '加载中...' : '加载更多'} </button> )} </div> ); }
核心逻辑说明
- 范围读取:通过
offset和limit参数,每次只读取未加载过的文章,避免重复读取已加载的文件。 - 客户端状态维护:用
useState保存已加载的文章列表,加载更多时将新文章合并到现有列表,而非替换。 - 加载状态管理:通过
isLoading防止重复点击,同时给用户反馈。
内容的提问来源于stack exchange,提问作者bartmanner
相关产品推荐
相关产品推荐

