跳到主要内容
版本:2.0.0

MDX 和 React

在 Markdown 中使用 JSX

Docusaurus 原生支持 MDX v1,可以直接在 Markdown 文档中编写 JSX,并渲染为 React 组件。

备注

虽然 Docusaurus 会把 .md.mdx 都解析为 MDX,但第三方工具可能会对其中一些语法的处理略有不同。 为了得到最准确的解析结果和更好的编辑器支持,推荐包含 MDX 语法的文档使用 .mdx 后缀。

可以读读 MDX 的文档,看看 MDX 还能做哪些更神奇的事。

导出组件

要在 MDX 文件中自定义组件,你必须导出它:只有以 export 开头的段落才会被解析为组件,而不是文本。

export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '2px',
color: '#fff',
padding: '0.2rem',
}}>
{children}
</span>
);

<Highlight color="#25c2a0">Docusaurus 绿</Highlight><Highlight color="#1877F2">Facebook 蓝</Highlight> 是我最喜欢的颜色。

我可以把我的 _JSX_ 和 **Markdown** 写在一起!

注意它是怎么同时渲染 React 组件和 Markdown 语法的:

http://localhost:3000
Docusaurus 绿 Facebook 蓝 是我最喜欢的颜色。

我可以把我的 JSXMarkdown 写在一起!

MDX 仍然是 JSX

由于所有文件都使用 MDX 解析,所以任何看起来像 HTML 的文件实际上都是 JSX。 因此,如果想使用内联样式,要使用 JSX 语法,即给 style 的值设置为 JavaScript 对象。

/* 不要这么写: */
<span style="background-color: red">Foo</span>
/* 要这么写: */
<span style={{backgroundColor: 'red'}}>Foo</span>

这里和 Docusaurus 1 不一样。 可以参考从 v1 迁移到 v2的文档。

In addition, MDX is not 100% compatible with CommonMark. 你可以用 MDX 实时编辑器 来确保你的文本是合法的 MDX 语法。

导入组件

你也可以导入其它目录中的组件,或者导入通过 npm 安装的第三方库提供的组件!

<!-- Docusaurus 主题组件 -->
import TOCInline from '@theme/TOCInline';
<!-- 外部组件 -->
import Button from '@mui/material/Button';
<!-- 自定义组件 -->
import BrowserWindow from '@site/src/components/BrowserWindow';
提示

@site 别名指向你的网站目录,通常这是 docusaurus.config.js 文件所在的那个目录。 如果用别名代替相对路径 ('../src/components/BrowserWindow'),你就不需要在移动文件、版本化文档,和翻译文档时,更新所有的导入路径。

对于简单的情况,在 Markdown 中直接声明组件非常方便,但由于编辑器的支持有限,有解析错误的风险,以及可复用性低,这种做法长期来看很难维护。 当你的组件涉及复杂的 JS 逻辑时,最好开一个单独的 .js 文件:

src/components/Highlight.js
import React from 'react';

export default function Highlight({children, color}) {
return (
<span
style={{
backgroundColor: color,
borderRadius: '2px',
color: '#fff',
padding: '0.2rem',
}}>
{children}
</span>
);
}
markdown-file.mdx
import Highlight from '@site/src/components/Highlight';

<Highlight color="#25c2a0">Docusaurus 绿</Highlight>
提示

如果你在许多文件中都用到了同一个组件,你不需要处处导入它——你也可以考虑把它添加到全局作用域。 见下文

MDX 组件作用域

除了导入组件导出组件,第三种在 MDX 中使用组件的方式是把它注册到全局作用域,这样它将自动在每个 MDX 文件中可用,无需任何导入声明。

比如,如果有这样一个 MDX 文件:

- 一个
- 列表!

和一些 <highlight>自定义标记</highlight>……

它会被编译成一个 React 组件,其中包含 ulliphighlight 等标签。 现在,你可以以 React 组件的形式,选择性地为每一个标签提供你自己的实现方式。 (highlight 甚至不是一个原生元素:你必须给它一个实现!)

在 Docusaurus 中,这个 MDX 组件作用域是由 @theme/MDXComponents 组件提供的。 它不像 @theme/ 别名下的绝大多数其他导出。严格来说,它不是一个 React 组件,而是一个从 ulimg 这样的标签名到它们对应的实现的映射。

如果你 swizzle 了这个组件,你会看到所有已被重新实现的标签,并且你可以通过 swizzle 相应的子组件(比如用来实现 <head> 功能的 @theme/MDXComponents/Head 组件),进一步改变它们的实现。

如果你想注册更多标签名(比如前文提到的 <highlight> 标签),你应该考虑包裹 @theme/MDXComponents,这样你就不需要维护所有现有的映射关系了。 因为 swizzle CLI 还不允许包裹非组件的文件,所以你需要手动创建这个包装层:

src/theme/MDXComponents.js
import React from 'react';
// 导入原映射
import MDXComponents from '@theme-original/MDXComponents';
import Highlight from '@site/src/components/Highlight';

export default {
// 复用默认的映射
...MDXComponents,
// 把 "highlight" 标签映射到我们的 <Highlight /> 组件!
// `Highlight` 会收到在 MDX 中被传递给 `highlight` 的所有 props
highlight: Highlight,
};

现在,你就可以在每个文件里自由使用 <highlight> 了,不需要写导入语句:

我可以随时随地用 <highlight color="#25c2a0">Docusaurus 绿</highlight>了!
http://localhost:3000

我可以随时随地用 Docusaurus 绿了!

信息

在这里我们用了小写的标签名, 比如 highlight,这样可以「假装」它是个原生元素。但你也可以用大写标签名,比如 Highlight

注意

这个功能需要由一个包裹组件的 provider 支持。 如果你在 React 页面中导入 Markdown,你需要通过 MDXContent 主题组件自己提供这个 provider。

src/pages/index.js
import React from 'react';
import FeatureDisplay from './_featureDisplay.mdx';
import MDXContent from '@theme/MDXContent';

export default function LandingPage() {
return (
<div>
<MDXContent>
<FeatureDisplay />
</MDXContent>
</div>
);
}

如果你没有用 MDXContent 包裹导入的 MDX,就无法使用全局作用域。

Markdown and JSX interoperability

Docusaurus v2 用的是 MDX v1,存在很多已知的解析失败的情况,会导致 Markdown 无法被正确识别。 你可以用 MDX 实时编辑器 来确保你的文本是合法的 MDX 语法。

解析失败的例子

一个以 JSX 标签开头的段落会被完全视为 JSX 字符串:

<span style={{color: 'red'}}>高亮文本</span> 但之后_Markdown_ 就**无法工作**
http://localhost:3000
高亮文本 但之后 _Markdown_ 就**无法工作**了

JSX 标签里的 Markdown 永远不会工作:

<span style={{color: 'red'}}>**粗体不起作用**</span>
http://localhost:3000
**粗体不起作用**

在一个 JSX 标签正下方的文本会被当作 JSX 文本:

<div style={{color: 'red'}}>
**粗体还是不起作用**
</div>
http://localhost:3000
**粗体还是不起作用**

被缩进四格的 Markdown 文本会被当作代码块:

<div style={{color: 'red'}}>

You may think I'm just some text...

</div>
http://localhost:3000
你可能以为我只是一些文本……

导入代码片段

除了能从文件中导入 React 组件外,你还能把文件中的代码直接导入为普通文本,并把它插入到代码块中,作为文档的代码示例进行展示。这要归功于 Webpack raw-loader。 要使用 raw-loader,需要先安装它:

npm install --save raw-loader

这时你就可以从文件中原样导入代码了:

myMarkdownFile.mdx
import CodeBlock from '@theme/CodeBlock';
import MyComponentSource from '!!raw-loader!./myComponent';

<CodeBlock language="jsx">{MyComponentSource}</CodeBlock>
http://localhost:3000
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React, {useState} from 'react';

export default function MyComponent() {
const [bool, setBool] = useState(false);
return (
<div>
<p>MyComponent rendered !</p>
<p>bool={bool ? 'true' : 'false'}</p>
<p>
<button onClick={() => setBool((b) => !b)}>toggle bool</button>
</p>
</div>
);
}

查看在 JSX 中使用代码块了解关于 <CodeBlock> 组件的更多详情。

备注

要注意的是,显示导入的代码必须使用 <CodeBlock> 组件,不能用 Markdown 的 ``` 语法,因为后者不会对表达式求值,而是原样显示传给它的内容,而使用 JSX 的形式可以对导入进来的变量进行求值并展示。

危险

这个功能是试验性的,未来 API 可能会有破坏性变更。

导入 Markdown

你可以把 Markdown 文档当作 React 组件,在其它 Markdown 文件或 React 组件中导入。

按照惯例,使用 _ 作为文件名前缀的文档不会生成页面,而是会被作为「片段」(partial) 来让其它文件导入。

_markdown-partial-example.mdx
<span>你好 {props.name}</span>

这是一些来自 `_markdown-partial-example.mdx` 的内容。
someOtherDoc.mdx
import PartialExample from './_markdown-partial-example.mdx';

<PartialExample name="思达" />;
http://localhost:3000
你好 思达

这是来自 _markdown-partial-example.md 的一些文本。

这样,你可以在多个文档中复用内容,避免重复。

注意

目前,导入的内容中的 Markdown 小标题不会被包含在目录中。 这是由于技术限制,我们正在努力解决:(issue)。

全局导出项

在 MDX 文档中,下面这些变量是全局可用的:

  • frontMatter:Markdown 文档的前言,包含字符串键和对应的值;
  • toc:目录,作为一个标题列表。 这个变量的实际用法可以参考内联目录
  • contentTitle:Markdown 文档标题,即文档中的第一个 h1 标题。 如果没有则是 undefined,例如把标题定义在了文档的 title 前言中。
import TOCInline from '@theme/TOCInline';
import CodeBlock from '@theme/CodeBlock';

这一页的目录,经过序列化:

<CodeBlock className="language-json">{JSON.stringify(toc, null, 2)}</CodeBlock>

这一页的前言:

<ul>
{Object.entries(frontMatter).map(([key, value]) => <li key={key}><b>{key}</b>: {value}</li>)}
</ul>

<p>这一页的标题是: <b>{contentTitle}</b></p>
http://localhost:3000

这一页的目录,经过序列化:

[
{
"value": "在 Markdown 中使用 JSX",
"id": "using-jsx-in-markdown",
"level": 2
},
{
"value": "导出组件",
"id": "exporting-components",
"level": 3
},
{
"value": "导入组件",
"id": "importing-components",
"level": 3
},
{
"value": "MDX 组件作用域",
"id": "mdx-component-scope",
"level": 3
},
{
"value": "Markdown and JSX interoperability",
"id": "markdown-and-jsx-interoperability",
"level": 3
},
{
"value": "导入代码片段",
"id": "importing-code-snippets",
"level": 2
},
{
"value": "导入 Markdown",
"id": "importing-markdown",
"level": 2
},
{
"value": "全局导出项",
"id": "available-exports",
"level": 2
}
]

这一页的前言:

  • id: react
  • description: 得益于 MDX,您可以在 Docusaurus Markdown 文档中使用 React
  • slug: /markdown-features/react

这一页的标题:MDX 和 React