Swizzle
在本节中,我们将介绍如何在 Docusaurus 中完成自定义布局。
听起来有点耳熟……?
这一节和样式与布局类似,但这一次,我们要真的写一点 React 代码,而不是摆弄样式表。 我们会讨论 Docusaurus 的一个核心概念:swizzle。Swizzle 允许你进行更深入的网站定制。
实际操作中,swizzle 允许你把一个主题组件替换为你自己的实现。它有两种模式:
为什么叫 swizzle?
这个名字来自 Objective-C 和 Swift-UI:通过 method swizzle(有时候翻译成「方法交换」),你可以替换一个现有选 择器(或方法)的实现。
对于 Docusaurus 来说,swizzle 组件意味着提供一个替代组件,它会优先于主题提供的组件。
你可以把它当作是 React 组件的猴子补丁 (monkey patch),使你能够覆盖默认的实现。 Gatsby 有一个类似的概念,叫做 theme shadowing。
要更深入地理解这一点,你必须明白主题组件是如何被解析的。
Swizzle 的过程
概览
Docusaurus 提供了一个方便的交互式 CLI 来 swizzle 组件。 你一般只需要记住以下指令:
- npm
- Yarn
- pnpm
npm run swizzle
yarn swizzle
pnpm run swizzle
它将在你的 src/theme
目录中生成一个新组件,组件看起来和这个示例类似:
- 弹出
- 包装
import React from 'react';
export default function SomeComponent(props) {
// 你可以完全自定义这个实现,包括改变 JSX, CSS 和 React 钩子
return (
<div className="some-class">
<h1>某个组件</h1>
<p>一些组件实现细节</p>
</div>
);
}
import React from 'react';
import SomeComponent from '@theme-original/SomeComponent';
export default function SomeComponentWrapper(props) {
// 你可以增强原组件,包括添加额外的属性或其他 JSX 元素
return (
<>
<SomeComponent {...props} />
</>
);
}
要总览所有可用的主题和组件,请运行:
- npm
- Yarn
- pnpm
npm run swizzle -- --list
yarn swizzle --list
pnpm run swizzle -- --list
你可以用 --help
查看所有可用的 CLI 选项,或参考 swizzle CLI 文档。
在 swizzle 组件后,重新启动你的开发服务器,让 Docusaurus 能意识到新组件的存在。
确保你清楚哪些组件是能被安全 swizzle 的。 某些组件是主题的内部实现细节。
docusaurus swizzle
只是一种帮助你 swizzle 组件的自动化方式。 你也可以手动创建 src/theme/SomeComponent.js
文件,Docusaurus 也会解析它。 这个命令背后没有什么内部的魔法!
弹出组件
弹出主题组件的过程会创建一个原始主题组件的复制版。你可以完全自定义并覆盖原组件。
要弹出主题组件,可以使用交互式 swizzle CLI,或使用 --eject
选项:
- npm
- Yarn
- pnpm
npm run swizzle [主题名] [组件名] -- --eject
yarn swizzle [主题名] [组件名] --eject
pnpm run swizzle [主题名] [组件名] -- --eject
举个例子:
- npm
- Yarn
- pnpm
npm run swizzle @docusaurus/theme-classic Footer -- --eject
yarn swizzle @docusaurus/theme-classic Footer --eject
pnpm run swizzle @docusaurus/theme-classic Footer -- --eject
这会把现有的 <Footer />
组件的实现复制到你的站点的 src/theme
目录。 Docusaurus 现在会使用这个 <Footer>
组件副本,而不是原组件。 你现在可以自由地重新实现 <Footer>
组件了。
import React from 'react';
export default function Footer(props) {
return (
<footer>
<h1>这是我自定义的网站页脚</h1>
<p>它和原本的那个完全不一样</p>
</footer>
);
}
在更新 Docusaurus 后,如果需要保持弹出的组件最新,可以再次运行弹出指令,并用 git diff
比较变化。 也建议你在文件顶部写一个简短的注释,说明你做了哪些修改。这样你可以在重弹出后更容易地重新做更改。
包装组件
包装主题组件的过程会创建一个原始主题组件的包装层。你可以增强原组件。
要包装主题组件,可以使用交互式 swizzle CLI,或使用 --wrap
选项:
- npm
- Yarn
- pnpm
npm run swizzle [主题名] [组件名] -- --wrap
yarn swizzle [主题名] [组件名] --wrap
pnpm run swizzle [主题名] [组件名] -- --wrap
举个例子:
- npm
- Yarn
- pnpm
npm run swizzle @docusaurus/theme-classic Footer -- --wrap
yarn swizzle @docusaurus/theme-classic Footer --wrap
pnpm run swizzle @docusaurus/theme-classic Footer -- --wrap
这会在你的站点的 src/theme
目录中创建一个包装组件。 Docusaurus 现在会使用这个 <FooterWrapper>
组件,而不是原始组件。 你现在可以在原始组件周围添加自定义内容。
import React from 'react';
import Footer from '@theme-original/Footer';
export default function FooterWrapper(props) {
return (
<>
<section>
<h2>额外部分</h2>
<p>这是在原先的页脚上面的一个额外部分</p>
</section>
<Footer {...props} />
</>
);
}
这个 @theme-original
是什么?
Docusaurus 通过主题别名来解析最终会被使用的主题组件。 新创建的包装组件会占据 @theme/SomeComponent
这个别名。 @theme-original/SomeComponent
允许导入包装组件所覆盖的原始组件,而不会因为包装组件导入自己,产生无限导入循环。
如果你想要在现有组件周围添加额外组件,包装主题的方法很不错,不需要弹出它。 比如,你可以轻松在每篇博文下面添加一个自定义的评论系统:
import React from 'react';
import BlogPostItem from '@theme-original/BlogPostItem';
import MyCustomCommentSystem from '@site/src/MyCustomCommentSystem';
export default function BlogPostItemWrapper(props) {
return (
<>
<BlogPostItem {...props} />
<MyCustomCommentSystem />
</>
);
}
哪些组件能被安全 swizzle?
能力越大,责任越大
有些主题组件是主题的内部实现细节。 Docusaurus 允许你 swizzle 它们,但这可能有风险。
为什么有风险?
主题作者(包括我们自己)可能会在未来更新主题:改变组件的属性、名字、文件系统位置、类型,等等。 比如,考虑这么一个组件,它接收两个属性,name
和 age
,但在某次重构之后,它开始接收一个 person
属性,包含上面两个字段。 你的组件仍然会假设原先的两个属性存在,所以会渲染 undefined
。
此外,一些内部组件可能会直接消失。 如果一个组件叫做 Sidebar
,之后又被重命名为 DocSidebar
,你 swizzle 的组件就会被完全忽略。
被标记为不安全的主题组件可能会在主题的小版本间发生向后不兼容的变化。在更新主题(或 Docusaurus 本身)时,你的个性化可能会产生无法预料的行为,甚至可能把你的整个网站弄坏。
对于每个主题组件,swizzle CLI 会标示主题作者所声 明的三种不同安全等级:
- Safe(安全):这个组件的公共 API 是稳定的,可以被安全地 swizzle。在每个主题大版本内,不应发生破坏性变更
- Unsafe(不安全):这个组件是主题的内部实现细节,不能被安全地 swizzle,在主题的小版本间可能发生破坏性变更
- Forbidden(禁止):swizzle CLI 会防止你 swizzle 这个组件,因为它根本没有被设计成可 swizzle。
有些组件可能能被安全包装,但不能被安全弹出。
不要太害怕 swizzle 不安全的组件:只是要记得破坏性变更