简单说下我的需求。我要在 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
里提取出来的 year
和 month
构造一个格式为 year-month
(比如 2024-11
)的字符串作为键,这个键用来将所有博客按月份进行分组。
ts
const [year, month, day] = post.date.split("-");
const key = `${year}-${month}`;
3)如果 obj
对象中还没有这个键 (即这个月份的分组还没有创建),则创建一个新的对象 (也就是创建这个月份的分组),包含 year
、month
和一个空的 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;