Swizzling 替换
在本章节中,我们将介绍如何在 道格龙(Docusaurus) 中进行布局定制。
似曾相识...?
本章节与 样式与布局 类似,但这次,我们将定制 React 组件本身,而不是它们的外观。我们将讨论 道格龙(Docusaurus) 中的一个核心概念:Swizzling,它允许进行更深层次的网站定制。
在实践中,Swizzling 允许用您自己的实现来替换主题组件,它有两种模式:
- 弹出 (Ejecting):创建原始主题组件的副本,您可以对其进行完全定制。
- 包装 (Wrapping):在原始主题组件周围创建一个包装器,您可以对其进行增强。
为什么叫 Swizzling?
这个名字来源于 Objective-C 和 Swift-UI:方法调配 (method swizzling) 是指改变一个已有选择器(方法)实现的过程。
对于 道格龙(Docusaurus) 而言,组件 Swizzling 意味着提供一个替代组件,该组件的优先级高于主题提供的组件。
您可以将其看作是 React 组件的 猴子补丁 (Monkey Patching),使您能够覆盖默认实现。Gatsby 有一个类似的概念,叫做主题遮蔽 (theme shadowing)。
要更深入地理解这一点,您需要了解主题组件是如何被解析的。
Swizzling 流程
概述
道格龙(Docusaurus) 提供了一个方便的交互式命令行界面 (CLI) 来进行组件 Swizzling。您通常只需要记住以下命令:
- npm
- Yarn
- pnpm
- Bun
npm run swizzle
yarn swizzle
pnpm run swizzle
bun run swizzle
它会在您的 src/theme
目录下生成一个新组件,看起来像下面这个例子:
- Ejecting
- Wrapping
import React from 'react';
export default function SomeComponent(props) {
// 您可以完全定制此实现
// 包括更改 JSX、CSS 和 React hooks
return (
<div className="some-class">
<h1>Some Component</h1>
<p>Some component implementation details</p>
</div>
);
}
import React from 'react';
import SomeComponent from '@theme-original/SomeComponent';
export default function SomeComponentWrapper(props) {
// 您可以增强原始组件,
// 包括添加额外的 props 或在其周围添加 JSX 元素
return (
<>
<SomeComponent {...props} />
</>
);
}
要概览所有可供 Swizzling 的主题和组件,请运行:
- npm
- Yarn
- pnpm
- Bun
npm run swizzle -- --list
yarn swizzle --list
pnpm run swizzle --list
bun run swizzle --list
使用 --help
查看所有可用的 CLI 选项,或参阅 Swizzle CLI 文档。
如果您觉得某个之前 Swizzling 过的组件不再需要,您可以直接从 src/theme
目录中删除其文件。删除组件后,请务必重启您的开发服务器,以确保更改正确生效。
Swizzling 一个组件后,请重启您的开发服务器,以便 道格龙(Docusaurus) 能够识别这个新组件。
请务必理解哪些组件是可以安全 Swizzling 的。一些组件是主题的内部实现细节。
docusaurus swizzle
只是一个帮助您 Swizzling 组件的自动化工具。您也可以手动创建 src/theme/SomeComponent.js
文件,道格龙(Docusaurus) 会解析它。这个命令背后并没有什么黑魔法!
弹出 (Ejecting)
弹出一个主题组件是指创建原始主题组件的副本,您可以对其进行完全的定制和覆盖。
要弹出一个主题组件,可以使用交互式 Swizzle CLI,或使用 --eject
选项:
- npm
- Yarn
- pnpm
- Bun
npm run swizzle [theme name] [component name] -- --eject
yarn swizzle [theme name] [component name] --eject
pnpm run swizzle [theme name] [component name] --eject
bun run swizzle [theme name] [component name] --eject
示例:
- npm
- Yarn
- pnpm
- Bun
npm run swizzle @docusaurus/theme-classic Footer -- --eject
yarn swizzle @docusaurus/theme-classic Footer --eject
pnpm run swizzle @docusaurus/theme-classic Footer --eject
bun 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) 的升级变得更加困难,因为如果组件接收的 props 或使用的内部主题 API 发生变化,您将需要迁移您的定制。
尽可能优先使用 包装 (wrapping):这样需要维护的代码量更少。
为了在 道格龙(Docusaurus) 升级后保持弹出的组件为最新状态,可以重新运行弹出命令,并使用 git diff
比较变更。我们还建议您在文件顶部写一个简短的注释,解释您做了哪些更改,以便在重新弹出后更容易地重新应用您的更改。
包装 (Wrapping)
包装一个主题组件是指在原始主题组件周围创建一个包装器,您可以对其进行增强。
要包装一个主题组件,可以使用 交互式 Swizzle CLI,或使用 --wrap
选项:
- npm
- Yarn
- pnpm
- Bun
npm run swizzle [theme name] [component name] -- --wrap
yarn swizzle [theme name] [component name] --wrap
pnpm run swizzle [theme name] [component name] --wrap
bun run swizzle [theme name] [component name] --wrap
示例:
- npm
- Yarn
- pnpm
- Bun
npm run swizzle @docusaurus/theme-classic Footer -- --wrap
yarn swizzle @docusaurus/theme-classic Footer --wrap
pnpm run swizzle @docusaurus/theme-classic Footer --wrap
bun 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
则允许导入被包装器遮蔽的原始组件,从而避免了包装器导入自身的无限循环。
包装主题是在现有组件周围添加额外组件的一个好方法,而无需弹出 (ejecting) 它。例如,您可以轻松地在每篇博客文章下添加一个自定义评论系统:
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 />
</>
);
}
哪些组件可以安全地 Swizzling?
能力越大,责任越大
某些主题组件被视为该主题的**‘内部实现细节’。这意味着它们是主题为了实现自身功能而使用的内部、非公开组件,其结构和名称在未来版本中极不稳定**。尽管 道格龙(Docusaurus) 的 swizzle 功能强大到允许你替换它们,但这样做风险很高,因为主题的任何一次小更新都可能导致你的自定义版本失效。
为什么有风险?
主题作者(包括我们)可能需要随着时间的推移更新他们的主题:更改组件的 props、名称、文件系统位置、类型等。例如,假设一个组件接收两个 props name
和 age
,但在重构后,它现在接收一个包含这两个属性的 person
prop。您的组件仍然期望接收那两个旧的 props,这将会导致渲染出 undefined
。
此外,内部组件可能会直接消失。如果一个组件名为 Sidebar
,后来被重命名为 DocSidebar
,那么您 Swizzling 过的组件将完全被忽略。
被标记为不安全的主题组件,可能会在主题的次要版本之间发生不向后兼容的更改。 当升级主题(或 道格龙(Docusaurus))时,您的定制可能会出现意外行为,甚至破坏您的网站。
对于每个主题组件,Swizzle CLI 将标示由主题作者声明的3 个不同的安全级别:
- 安全: 该组件可以安全地进行 Swizzling替换,其公共 API 被认为是稳定的,在主题的 主要版本 中不会发生破坏性更改。
- 不安全: 该组件是测试用的内部主题组件,不适合进行 Swizzling替换,在主题的 次要版本 中可能会发生破坏性更改。
- 禁止: Swizzle CLI 将阻止您对该组件进行 Swizzling,因为它根本就没设计为可以进行 Swizzling替换。
某些组件可能安全地进行包装,但不能安全进行弹出。
不要过于 害怕 Swizzling 替换不安全的组件: 只需记住可能会发生 破坏性更改,并且您可能需要在次要版本升级时手动升级您的自定义设置。
如果您有一个强烈的需求来对不安全的组件进行 Swizzling,请在这里报告,我们将共同努力找到一个解决方案,使其安全。
我应该 Swizzle 替换 哪个组件?
并不总是清楚您应该具体 Swizzle替换 哪个组件以实现预期的结果。@docusaurus/theme-classic
提供了大多数主题组件,大约有 100 个组件!
要打印所有 @docusaurus/theme-classic
组件的概述:
- npm
- Yarn
- pnpm
- Bun
npm run swizzle @docusaurus/theme-classic -- --list
yarn swizzle @docusaurus/theme-classic --list
pnpm run swizzle @docusaurus/theme-classic --list
bun run swizzle @docusaurus/theme-classic --list
您可以按照以下步骤找到合适的组件进行 Swizzle 替换:
- 组件描述。 一些组件提供了简短的描述,这是找到合适组件的好方法。
- 组件名称。 官方主题组件的命名是语义化的,因此您应该能够从名称推断其功能。Swizzle CLI 允许您输入组件名称的一部分,以缩小可选范围。例如,如果您运行
yarn swizzle @docusaurus/theme-classic
,并输入Doc
,则只会列出与文档相关的组件。 - 从更高层级的组件开始。 组件形成一个树状结构,一些组件会导入其他组件。每个路由将与一个顶层组件相关联,该路由将渲染该组件(大多数组件在 内容插件中的路由 中列出)。例如,所有博客文章页面都有
@theme/BlogPostPage
作为最顶层组件。您可以从 Swizzling 这个组件开始,然后向下查找组件树,以找到渲染您所针对的内容的组件。 找到正确的组件后,不要忘记通过删除文件来取消 Swizzle 其他组件,以免维护过多的组件。 - 阅读 主题源代码 并明智地使用搜索功能。
我需要进行 Swizzle替换 吗?
进行 Swizzling 最终意味着您需要维护一些与 Docusaurus 内部 API 交互的额外 React 代码。如果可以的话,在自定义您的网站时,请考虑以下替代方案:
- 使用 CSS。 CSS 规则和选择器通常可以帮助您实现相当程度的自定义。有关更多详细信息,请参考 样式和布局。
- 使用翻译。 这听起来可能令人惊讶,但翻译最终只是自定义文本标签的一种方式。例如,如果您网站的默认语言是
en
,您仍然可以运行yarn write-translations -l en
并编辑生成的code.json
。有关更多详细信息,请参考 国际化教程。
用 <Root>
包裹您的网站
<Root>
组件被渲染在 React 树的 最顶层,层级位于主题的 <Layout>
之上,并且 永远不会卸载。这是添加状态逻辑的完美位置,这些逻辑在导航之间不应重新初始化(例如用户认证状态、购物车状态等)。
通过手动 创建一个文件 在 src/theme/Root.js
进行 Swizzle替换:
import React from 'react';
// 默认实现,您可以自定义
export default function Root({children}) {
return <>{children}</>;
}
使用这个组件来渲染 React Context 提供者。