在 Next.js 12 中进行分页 | Jim Zhang's blog
在 Next.js 12 中进行分页2023-03-10

老公,,我的老公!!老公你终于又发歌了,还是和🐑👄姐!

细心的读者已经发现了,今天 Jim 把欠了一年多的主页分页给做了。说实话,现在博客面临一个比较严峻的问题——要不要迁移到重大更新 Next.js 13 上,但是本着“能不改就不改”的原则,Jim 最终就没弄了,问题是在 Next.js 12 中,分页特别麻烦,尤其是需要处理 staticProps 的时候,所以这次的开发也是以规避直接触碰 staticProps 为方法核心。

本文的想法来源于 LogRocket 的这篇文章,简言之,使用了 react.js 中的 useState hook 来实现分页。

基本思路

首先,我们需要知道,我们的分页是基于什么的。在这里,我们的分页是基于 posts 的,所以我们需要知道 posts 的总数,那很容易,通过 fs 来查一下 posts 文件夹下的文件数量,通过 getStaticProps 传递给预渲染就行了。

好,到预渲染环节,我们用个简单的 useState 就能解决当前页面的切换问题,这样就可以实现分页了。

对用户显示的包含分页的页面

// pages/index.js
import Pagination from '../components/pagination'

import { useState } from 'react'

export default function Home() {
  const [currentPage, setCurrentPage] = useState(1);
  const pageSize = 10; // Let default num be 10.

  const onPageChange = (page) => {
    setCurrentPage(page);
  };

  return (
    <Pagination
      items={count} 
      currentPage={currentPage} 
      pageSize={pageSize} 
      onPageChange={onPageChange}
    />
  )
}

export async function getStaticProps({ locale }) {
  // Get files from the posts dir
  const files = fs.readdirSync(path.join('posts'))

  // Get slug and frontmatter from posts
  const posts = files.map((filename) => {
    const slug = filename.replace('.md', '')
    const markdown = fs.readFileSync(
      path.join('posts', filename),
      'utf-8'
    )
    return { slug }
  })

  // return total amount of posts
  const total = posts.length

  return {
    props: {
      posts: posts.sort(sortByDate).sort(pin),
      count: total,
    },
  }
}

分页组件

// components/pagination.js
export default function Pagination({ items, pageSize, currentPage, onPageChange }) {
  const pagesCount = Math.ceil(items / pageSize)

  if (pagesCount === 1) return null
  const pages = Array.from({ length: pagesCount }, (_, i) => i + 1)

  return (
    <div className='pagination-container'>
      <ul className='pagination'>
        {(() => {
          if (currentPage !== 1) {
            return (
              <li
                className='pageItem'
                onClick={() => onPageChange(currentPage - 1)}
              >
                &lt;
              </li>
            )
          }
        })()}
        {pages.map((page) => (
          <li
            key={page}
            className={
              page === currentPage ? 'pageItemActive' : 'pageItem'
            }
            onClick={() => onPageChange(page)}
          >
            {page}
          </li>
        ))}
        {(() => {
          if (currentPage !== (parseInt(pages) + 1)) {
            return (
              <li
                className='pageItem'
                onClick={() => onPageChange(currentPage + 1)}
              >
                &gt;
              </li>
            )
          }
        })()}
      </ul>
    </div>
  )
}

裁剪 posts

这里我们不如写个小脚本:

// utils/index.js
export const paginate = (items, pageNumber, pageSize) => {
  const startIndex = (pageNumber - 1) * pageSize;
  return items.slice(startIndex, startIndex + pageSize);
};

然后回到 index.js 中,再把其余组件凑齐:

// pages/index.js
import { paginate } from '../utils'
import Head from '../components/head'
import Post from '../components/post'
import Header from '../components/header'
import Footer from '../components/footer'
import Pagination from '../components/pagination'

export default function Home({ posts, count }) {
  const router = useRouter()

  const [currentPage, setCurrentPage] = useState(1);
  const pageSize = 8;

  const onPageChange = (page) => {
    setCurrentPage(page);
  };

  return (
    <>
      <Head title='' />
      <Header />
      <div className='main'>
        <section className='posts'>
          {paginate(posts, currentPage, pageSize).filter(post => post.frontmatter.locale === router.locale).map((post, index) => (
            <Post key={index} post={post} default_locale={router.defaultLocale} />
          ))}
        </section>
      </div>
      <Pagination
        items={count} 
        currentPage={currentPage} 
        pageSize={pageSize} 
        onPageChange={onPageChange}
      />
      <Footer />
    </>
  )
}