Skip to main content

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 run swizzle

它会在您的 src/theme 目录下生成一个新组件,看起来像下面这个例子:

src/theme/SomeComponent.js
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>
);
}

要概览所有可供 Swizzling 的主题和组件,请运行:

npm run swizzle -- --list

使用 --help 查看所有可用的 CLI 选项,或参阅 Swizzle CLI 文档。

移除不再需要的 Swizzling 组件

如果您觉得某个之前 Swizzling 过的组件不再需要,您可以直接从 src/theme 目录中删除其文件。删除组件后,请务必重启您的开发服务器,以确保更改正确生效。

note

Swizzling 一个组件后,请重启您的开发服务器,以便 道格龙(Docusaurus) 能够识别这个新组件。

优先选择安全的方式

请务必理解哪些组件是可以安全 Swizzling 的。一些组件是主题的内部实现细节。

info

docusaurus swizzle 只是一个帮助您 Swizzling 组件的自动化工具。您也可以手动创建 src/theme/SomeComponent.js 文件,道格龙(Docusaurus) 会解析它。这个命令背后并没有什么黑魔法!

弹出 (Ejecting)​

弹出一个主题组件是指创建原始主题组件的副本,您可以对其进行完全的定制和覆盖。

要弹出一个主题组件,可以使用交互式 Swizzle CLI,或使用 --eject 选项:

npm run swizzle [theme name] [component name] -- --eject

示例:

npm run swizzle @docusaurus/theme-classic Footer -- --eject

这会将当前 <Footer /> 组件的实现复制到您网站的 src/theme 目录中。道格龙(Docusaurus) 现在将使用这个 <Footer> 组件的副本,而不是原始组件。您现在可以完全自由地重新实现 <Footer> 组件。

src/theme/Footer/index.js
import React from 'react';

export default function Footer(props) {
return (
<footer>
<h1>这是我的自定义网站页脚</h1>
<p>它和原始的页脚非常不同</p>
</footer>
);
}
warning

弹出一个不安全的组件有时会导致复制大量内部代码,而这些代码现在需要您自己来维护。这会使 道格龙(Docusaurus) 的升级变得更加困难,因为如果组件接收的 props 或使用的内部主题 API 发生变化,您将需要迁移您的定制。

尽可能优先使用 包装 (wrapping):这样需要维护的代码量更少。

重新 Swizzling

为了在 道格龙(Docusaurus) 升级后保持弹出的组件为最新状态,可以重新运行弹出命令,并使用 git diff 比较变更。我们还建议您在文件顶部写一个简短的注释,解释您做了哪些更改,以便在重新弹出后更容易地重新应用您的更改。

包装 (Wrapping)​

包装一个主题组件是指在原始主题组件周围创建一个包装器,您可以对其进行增强。

要包装一个主题组件,可以使用交互式 Swizzle CLI,或使用 --wrap 选项:

npm run swizzle [theme name] [component name] -- --wrap

示例:

npm run swizzle @docusaurus/theme-classic Footer -- --wrap

这将在您网站的 src/theme 目录中创建一个包装器。道格龙(Docusaurus) 现在将使用 <FooterWrapper> 组件而不是原始组件。您现在可以在原始组件周围添加定制内容。

src/theme/Footer/index.js
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 则允许导入被包装器遮蔽的原始组件,从而避免了包装器导入自身的无限循环。

tip

包装主题是在现有组件周围添加额外组件的一个好方法,而无需弹出 (ejecting) 它。例如,您可以轻松地在每篇博客文章下添加一个自定义评论系统:

src/theme/BlogPostItem.js
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替换。
note

某些组件可能安全地进行包装,但不能安全进行弹出。

info

不要过于 害怕 Swizzling 替换不安全的组件: 只需记住可能会发生 破坏性更改,并且您可能需要在次要版本升级时手动升级您的自定义设置。

报告您的用例

如果您有一个强烈的需求来对不安全的组件进行 Swizzling,请在这里报告,我们将共同努力找到一个解决方案,使其安全。

我应该 Swizzle替换 哪个组件?​

并不总是清楚您应该具体 Swizzle替换 哪个组件以实现预期的结果。@docusaurus/theme-classic 提供了大多数主题组件,大约有 100 个组件!

tip

要打印所有 @docusaurus/theme-classic 组件的概述:

npm run swizzle @docusaurus/theme-classic -- --list

您可以按照以下步骤找到合适的组件进行 Swizzle 替换:

  1. 组件描述。 一些组件提供了简短的描述,这是找到合适组件的好方法。
  2. 组件名称。 官方主题组件的命名是语义化的,因此您应该能够从名称推断其功能。Swizzle CLI 允许您输入组件名称的一部分,以缩小可选范围。例如,如果您运行 yarn swizzle @docusaurus/theme-classic,并输入 Doc,则只会列出与文档相关的组件。
  3. 从更高层级的组件开始。 组件形成一个树状结构,一些组件会导入其他组件。每个路由将与一个顶层组件相关联,该路由将渲染该组件(大多数组件在 内容插件中的路由 中列出)。例如,所有博客文章页面都有 @theme/BlogPostPage 作为最顶层组件。您可以从 Swizzling 这个组件开始,然后向下查找组件树,以找到渲染您所针对的内容的组件。 找到正确的组件后,不要忘记通过删除文件来取消 Swizzle 其他组件,以免维护过多的组件。
  4. 阅读 主题源代码 并明智地使用搜索功能。
来提问吧!

如果您仍然不知道应该对哪个组件进行 Swizzle 以实现所需效果,可以在我们的 支持渠道 中寻求帮助。

我们也希望更好地了解您最炫的自定义用例,因此请 报告它们。

我需要进行 Swizzle替换 吗?​

进行 Swizzling 最终意味着您需要维护一些与 Docusaurus 内部 API 交互的额外 React 代码。如果可以的话,在自定义您的网站时,请考虑以下替代方案:

  1. 使用 CSS。 CSS 规则和选择器通常可以帮助您实现相当程度的自定义。有关更多详细信息,请参考 样式和布局。
  2. 使用翻译。 这听起来可能令人惊讶,但翻译最终只是自定义文本标签的一种方式。例如,如果您网站的默认语言是 en,您仍然可以运行 yarn write-translations -l en 并编辑生成的 code.json。有关更多详细信息,请参考 国际化教程。
tip

越小越好。 如果必须进行 Swizzling,建议仅替换相关部分,并尽量减少自己维护的代码量。替换一个小组件通常意味着在升级时更少出现 破坏性更改 的风险。

包装 也是比 弹出 更安全的替代方案。

用 <Root> 包裹您的网站​

<Root> 组件被渲染在 React 树的 最顶层,层级位于主题的 <Layout> 之上,并且 永远不会卸载。这是添加状态逻辑的完美位置,这些逻辑在导航之间不应重新初始化(例如用户认证状态、购物车状态等)。

通过手动 创建一个文件 在 src/theme/Root.js 进行 Swizzle替换:

src/theme/Root.js
import React from 'react';

// 默认实现,您可以自定义
export default function Root({children}) {
return <>{children}</>;
}

tip

使用这个组件来渲染 React Context 提供者。