Ilence Ye

将博客按发布月份分组展示

简单说下我的需求。我要在 ilence.me 的首页展示自己写过的所有博客:

  • 这些博客需要按发布的月份来分组。
  • 每组博客需要在最上方展示发布的月份。如果刚好是在这个月发布的或者是在十二月发布的,那么还需要展示发布的年份。
  • 而对于每条博客则展示标题和发布日期里的“日”。

最后要实现的效果大概长下面这样:

md
- 2024 11
  - post-1 01
-      10
  - post-2 10
  - post-3 01
- 2023 12
  - post-4 20
  - post-5 05

初始的博客数据大概长这样 (当然这是已经转换过一遍的数据了,不过这并不妨碍我们直接从这里开始):

json
[
  {
    "slug": "post-1",
    "title": "Post 1",
    "date": "2024-11-01"
  },
  {
    "slug": "post-2",
    "title": "Post 2",
    "date": "2024-10-10"
  },
  {
    "slug": "post-3",
    "title": "Post 3",
    "date": "2024-10-01"
  }
]

转换数据

ts
async function getPostsData() {
  // 拿初始数据的过程省略.
  // 你只要知道最后拿到的数据是大概长上面示意的那样就行.
}

function getPostsGroupedByMonth(posts) {
  let obj: Record<string, { year: string; month: string; posts: Post[] }> = {};

  for (const post of posts) {
    const [year, month, day] = post.date.split("-");
    const key = `${year}-${month}`;

    if (!obj[key]) obj[key] = { year, month, posts: [] };

    obj[key].posts.push({ ...post, day });
  }

  return Object.values(obj);
}

const posts = await getPostData();
const groupedPosts = getPostsGroupedByMonth(posts);

getPostsGroupedByMonth 函数将帮我们将一组博客按月份 (实现时需要附加上年份来保证月份的唯一性) 进行分组。接下来简单解释一下思路。

1)通过 for...of 循环遍历每一篇 post

2)通过从 post.date 里提取出来的 yearmonth 构造一个格式为 year-month (比如 2024-11 )的字符串作为键,这个键用来将所有博客按月份进行分组。

ts
const [year, month, day] = post.date.split("-");
const key = `${year}-${month}`;

3)如果 obj 对象中还没有这个键 (即这个月份的分组还没有创建),则创建一个新的对象 (也就是创建这个月份的分组),包含 yearmonth 和一个空的 posts 数组。posts 数组用来存储发布在这个月里的所有博客。

ts
if (!obj[key]) obj[key] = { year, month, posts: [] };
obj[key].posts.push({ ...post, day });

4)最后通过 Object.values() 方法将 obj 对象的所有值给提取出来,由此得到按月份分组好的博客的一个数组。

ts
return Object.values(obj);

渲染数据

拿到按月份分组好的博客后,就可以开始渲染了:

jsx
{
  groupedPosts.map(({ year, month, posts }) => (
    <section>
      <h2>
        <span>{shouldDisplayYear(year, month) && year}</span>
        <span>{month}</span>
      </h2>
      <ul>
        {posts.map(({ slug, title, date, day }) => (
          <li>
            <a href={`/${slug}`}>
              <p>{title}</p>
              <time datetime={date}>{day}</time>
            </a>
          </li>
        ))}
      </ul>
    </section>
  ));
}
ts
const date = new Date();
const currentYear = date.getFullYear();
const currentMonth = date.getMonth() + 1;

// 如果刚好是在这个月发布的或者是在十二月发布的,则展示发布的年份
const shouldDisplayYear = (year: string, month: string) =>
  (parseInt(year) === currentYear && parseInt(month) === currentMonth) ||
  parseInt(month) === 12;