跳到主要内容

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 目录中删除其文件。删除组件后,请务必重启您的开发服务器,以确保更改正确生效。

备注

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

优先选择安全的方式

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

信息

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>
);
}
注意

弹出一个不安全的组件有时会导致复制大量内部代码,而这些代码现在需要您自己来维护。这会使 道格龙(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 则允许导入被包装器遮蔽的原始组件,从而避免了包装器导入自身的无限循环。

提示

包装主题是在现有组件周围添加额外组件的一个好方法,而无需弹出 (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 nameage,但在重构后,它现在接收一个包含这两个属性的 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 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。有关更多详细信息,请参考 国际化教程
提示

越小越好。 如果必须进行 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}</>;
}

提示

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