跳到主要内容

Docusaurus主题定制:给归档页加上博文计数

· 阅读需 3 分钟

网站迁移到 Docusaurus 有一段时间了,整体感觉还行,虽然不那么花里胡哨,不过现在也懒得把网站整得花里胡哨了。用过Hexo、Hugo,也用过 Typecho,用过别人的主题,也自己魔改过,如今换到Docusaurus了,打算长期就用这个了。

不过,Docusaurus 的归档页有个让我这个强迫症很难受的地方,归档页的标题和副标题都是“历史博文”,显得很累赘。我想把副标题改为博文计数,经过一番折腾,终于做到了,效果如下:

实现

我第一反应是加个自定义的 js,通过修改 DOM 实现,但是不太可行,因为 Docusaurus 是 SPA,从首页点进归档页时,走React Router 客户端路由,不会发起浏览器GET请求去下载新的HTML文件,脚本并不会执行。思来想去,还是打算用正经的方法实现,也更优雅点。

swizzle

Docusaurus 要做定制,就涉及 swizzle 了。这是官方文档介绍:Swizzling | Docusaurus

简单来说,Swizzling 就是允许你用自己的代码替换掉主题自带的 React 组件,从而实现比改 CSS 更深入的定制 —— 不只改外观,而是直接改组件的行为和结构。Swizzling 有两种模式:

  • Eject: 把原始组件复制一份,完全重写
  • Wrap: 在原始组件外面套一层包装组件,只做增强

执行

npm run swizzle

可进入交互式选择界面。博客归档页是BlogArchivePage ,归档页标题相关的原始代码:

return (
<>
<PageMetadata title={title} description={description} />
<Layout>
<header className="hero hero--primary">
<div className="container">
<Heading as="h1" className="hero__title">
{title}
</Heading>
<p className="hero__subtitle">{description}</p>
</div>
</header>
<main>{years.length > 0 && <YearsSection years={years} />}</main>
</Layout>
</>
);

我不想用Eject,但整个归档页又是一个整体,不好Wrap。毕竟我是要修改,不是在前后加内容。

useEffect

useEffect 是 React 官方内置的 Hook,属于 React 运行时机制。在组件挂在时执行effect函数,在依赖里的值发生变化时重新执行effect,在组件卸载时执行effect return的清理函数。

所以,我依然使用Wrap,然后通过useEffect来修改DOM。

import React, { useEffect, useRef } from 'react';
import OriginalBlogArchive from '@theme-original/BlogArchivePage';

export default function BlogArchive(props) {
const totalPosts = props.archive.blogPosts.length;

useEffect(() => {
const timer = setInterval(() => {
const el = document.querySelector('p.hero__subtitle');
if (el) {
el.textContent = `已经写了 ${totalPosts}`;
clearInterval(timer);
}
}, 50);

return () => {
clearInterval(timer);
};
}, [totalPosts]);

return <OriginalBlogArchive {...props} />;
}

尽管useEffect触发时React已经渲染,但React hydrate修补DOM可能还有一微小时差,所以用短轮询增加容错。

写在最后

突然想看看我每年写了多少篇,于是让AI统计了下:

写得不多,不过好在勉强算坚持下来了。从最初自学计算机,那时写的很少;然后读研期间比较活跃,一年最多写了30篇;后面工作了,篇数就明显减少。有很多时候有计划写,但因为各种原因耽搁了,不过工作后写的质量还是要稍微高一些的。但愿后面可以多抽些时间,多写写,或许若干年后又会有不一样的感觉。