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 语法的:
我可以把我的 JSX 和 Markdown 写在一起!
由于所有文件都使用 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';
对于简单的情况,在 Markdown 中直接声明组件非常方便,但由于编辑器的支持有限,有解析错误的风险,以及可复用性低,这种做法长期来看很难维护。 当你的组件涉及复杂的 JS 逻辑时,最好开一个单独的 .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>
);
}
import Highlight from '@site/src/components/Highlight';
<Highlight color="#25c2a0">Docusaurus 绿</Highlight>
如果你在许多文件中都用到了同一个组件,你不需要处处导入它——你也可以考虑把它添加到全局作用域。 见下文
MDX 组件作用域
除了导入组件和导出组件,第三种在 MDX 中使用组件的方式是把它注册到全局作用域,这样它将自动在每个 MDX 文件中可用,无需任何导入声明。
比如,如果有这样一个 MDX 文件:
- 一个
- 列表!
和一些 <highlight>自定义标记</highlight>……
它会被编译成一个 React 组件,其中包含 ul
、li
、p
、highlight
等标签。 现在,你可以以 React 组件的形式,选择性地为每一个标签提供你自己的实现方式。 (highlight
甚至不是一个原生元素:你必须给它一个实现!)
在 Docusaurus 中,这个 MDX 组件作用域是由 @theme/MDXComponents
组件提供的。 它不像 @theme/
别名下的绝大多数其他导出。严格来说,它不是一个 React 组件,而是一个从 ul
和 img
这样的标签名到它们对应的实现的映射。
如果你 swizzle 了这个组件,你会看到所有已被重新实现的标签,并且你可以通过 swizzle 相应的子组件(比如用来实现 <head>
功能的 @theme/MDXComponents/Head
组件),进一步改变它们的实现。
如果你想注册更多标签名(比如前文提到的 <highlight>
标签),你应该考虑包裹 @theme/MDXComponents
,这样你就不需要维护所有现有的映射关系了。 因为 swizzle CLI 还不允许包裹非组件的文件,所以你需要手动创建这个包装层:
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>了!
我可以随时随地用 Docusaurus 绿了!
在这里我们用了小写的标签名, 比如 highlight
,这样可以「假装」它是个原生元素。但你也可以用大写标签名,比如 Highlight
。
这个功能需要由一个包裹组件的 provider 支持。 如果你在 React 页面中导入 Markdown,你需要通过 MDXContent
主题组件自己提供这个 provider。
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_ 就**无法工作**了
这一行都用 JSX,或者在这一行开头放一点纯文本:
<span style={{color: 'red'}}>这一段全部用 JSX</span> 就不用<i>操心</i> <b>Markdown</b> 的问题了
​<span style={{color: 'red'}}>← 这是个零宽空格</span> 然后在这之后 <i>Markdown</i> <b>就可以工作了</b>
← 这是个零宽空格 然后在这之后 Markdown 就可以工作了
JSX 标签里的 Markdown 永远不会工作:
- 问题
- 解决方案
<span style={{color: 'red'}}>**粗体不起作用**</span>
在 JSX 标签里用 JSX,或者把 Markdown 放到外层去:
<span style={{color: 'red'}}><b>粗体现在起作用了</b></span>
**<span style={{color: 'red'}}>粗体现在起作用了</span>**
粗体现在起作用了
在一个 JSX 标签正下方的文本会被当作 JSX 文本:
- 问题
- 解决方案
<div style={{color: 'red'}}>
**粗体还是不起作用**
</div>
加一个空行:
<div style={{color: 'red'}}>
**粗体现在起作用了**
</div>
粗体现在起作用了
被缩进四格的 Markdown 文本会被当作代码块:
- 问题
- 解决方案
<div style={{color: 'red'}}>
You may think I'm just some text...
</div>
你可能以为我只是一些文本……
不要缩进:
<div style={{color: 'red'}}>
现在我真的只是文本了
</div>
现在我真的只是文本了
导入代码片段
除了能从文件中导入 React 组件外,你还能把文件中的代码直接导入为普通文本,并把它插入到代码块中,作为文档的代码示例进行展示。这要归功于 Webpack raw-loader。 要使用 raw-loader
,需要先安装它:
- npm
- Yarn
npm install --save raw-loader
yarn add raw-loader
这时你就可以从文件中原样导入代码了:
import CodeBlock from '@theme/CodeBlock';
import MyComponentSource from '!!raw-loader!./myComponent';
<CodeBlock language="jsx">{MyComponentSource}</CodeBlock>
/**
* 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) 来让其它文件导入。
<span>你好 {props.name}</span>
这是一些来自 `_markdown-partial-example.mdx` 的内容。
import PartialExample from './_markdown-partial-example.mdx';
<PartialExample name="思达" />;
这是来自 _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>
这一页的目录,经过序列化:
[
{
"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