本文介绍了如何在您的项目中使用 MDX 文件。它展示了如何传递属性以及如何导入、定义或传递组件。有关如何将 MDX 集成到您的项目中,请参阅§ Getting started。要了解 MDX 格式的工作原理,我们建议您从§ 什么是 MDX 开始。
MDX 工作原理
一个集成系统将 MDX 语法编译成 JavaScript。假设我们有一个 MDX 文档,example.mdx
:
input.mdx
export function Thing() {
return <>World</>
}
# Hello <Thing />
这大致转换为以下 JavaScript。下面的内容可能有助于形成一个心理模型:
output-outline.jsx
/* @jsxRuntime automatic */
/* @jsxImportSource react */
export function Thing() {
return <>World</>
}
export default function MDXContent() {
return <h1>Hello <Thing /></h1>
}
一些观察结果:
- 输出是序列化的 JavaScript,仍然需要被评估
- 注入了一个注释来配置 JSX 的处理方式
- 这是一个完整的文件,具有导入/导出
- 导出了一个组件 (
MDXContent
)
实际输出如下:
output-actual.js
import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from 'react/jsx-runtime'
export function Thing() {
return _jsx(_Fragment, {children: 'World'})
}
function _createMdxContent(props) {
const _components = {h1: 'h1', ...props.components}
return _jsxs(_components.h1, {children: ['Hello ', _jsx(Thing, {})]})
}
export default function MDXContent(props = {}) {
const {wrapper: MDXLayout} = props.components || {}
return MDXLayout
? _jsx(MDXLayout, {...props, children: _jsx(_createMdxContent, {...props})})
: _createMdxContent(props)
}
一些额外的观察结果:
- JSX 被编译成函数调用和对 React 的引入†
- 内容组件可以通过
{components: {wrapper: MyLayout}}
来包装所有内容 - 内容组件可以通过
{components: {h1: MyComponent}}
来使用其他组件作为标题
† MDX 与 React 没有耦合。您也可以将其与 Preact, Vue, Emotion, Theme UI 等一起使用。支持经典和自动 JSX 运行时。
MDX 内容
我们刚刚看到 MDX 文件被编译成了组件。您可以像在您选择的框架中使用任何其他组件一样使用这些组件。看这个文件:
example.mdx
# Hi!
它可以像这样在 React 应用程序中导入和使用:
example.js
import React from 'react'
import ReactDom from 'react-dom'
import Example from './example.mdx' // 假设使用集成编译 MDX -> JS。
const root = ReactDom.createRoot(document.getElementById('root'))
root.render(<Example />)
主要内容被导出为默认导出。所有其他值也被导出。看这个例子:
example.mdx
export function Thing() {
return <>World</>
}
# Hello <Thing />
它可以以以下方式导入:
example.js
// 一个命名空间导入以获取所有内容:
import * as everything from './example.mdx' // 假设使用集成编译 MDX -> JS。
console.log(everything) // {Thing: [Function: Thing], default: [Function: MDXContent]}
// 默认导出快捷方式和一个命名导入限定符:
import Content, {Thing} from './example.mdx'
console.log(Content) // [Function: MDXContent]
console.log(Thing) // [Function: Thing]
// 具有另一个本地名称的导入限定符:
import {Thing as AnotherName} from './example.mdx'
console.log(AnotherName) // [Function: Thing]
属性
在 § 什么是 MDX 中,我们展示了在 MDX 中使用花括号内的 JavaScript 表达式:
example.mdx
import {year} from './data.js'
export const name = 'world'
# Hello {name.toUpperCase()}
The current year is {year}
除了在 MDX 中导入或定义数据,数据也可以传递给 MDXContent
。传递的数据称为 props
。例如:
example.mdx
# Hello {props.name.toUpperCase()}
The current year is {props.year}
这个文件可以这样使用:
example.jsx
import Example from './example.mdx' // 假设使用集成编译 MDX -> JS。
// 使用 `createElement` 调用:
React.createElement(Example, {name: 'Venus', year: 2021})
// 使用 JSX:
<Example name="Mars" year={2022} />
组件
有一个特殊的属性:components
。它接受一个将组件名称映射到组件的对象。看这个例子:
example.mdx
# Hello *<Planet />*
它可以从 JavaScript 中导入并传递组件,如下所示:
example.jsx
import Example from './example.mdx' // 假设使用集成编译 MDX -> JS。
<Example
components={{
Planet() {
return <span style={{color: 'tomato'}}>Pluto</span>
}
}}
/>
您不必传递组件。您也可以在 MDX 中定义或导入它们:
example.mdx
import {Box, Heading} from 'rebass'
使用导入的组件的 MDX!
<Box>
<Heading>这是一个标题</Heading>
</Box>
因为 MDX 文件 就是 组件,它们也可以相互导入:
example.mdx
import License from './license.md' // 假设使用集成编译 MDX -> JS。
import Contributing from './docs/contributing.mdx'
# 你好世界
<License />
---
<Contributing />
这里有一些传递组件的其他示例:
example
.jsx
<Example
components={{
// 将 `h1` (`# heading`) 映射到使用 `h2`。
h1: 'h2',
// 将 `em` (`*like so*`) 重写为具有金色前景色的 `i`。
em(props) {
return <i style={{color: 'goldenrod'}} {...props} />
},
// 传递布局(使用特殊的 `'wrapper'` 键)。
wrapper({components, ...rest}) {
return <main {...rest} />
},
// 传递一个组件。
Planet() {
return 'Neptune'
},
// 此嵌套组件可以使用 `<theme.text>hi</theme.text>`
theme: {
text(props) {
return <span style={{color: 'grey'}} {...props} />
}
}
}}
/>
在 components
中可以传递以下键:
- 您使用 markdown 编写的 HTML 等效项,例如
# heading
的h1
(请参阅 § 组件表 以查看示例) wrapper
,它定义了布局(但本地布局优先)*
其他任何有效的 JavaScript 标识符(foo
、Quote
、_
、$x
、a1
),用于 JSX 中使用的东西(如<So />
或<like.so />
,请注意本地定义的组件优先级)‡
如果您曾经想知道 JSX 中的名称(例如 <x>
中的 x
)是字面标记名称(例如 h1
)还是非字面标记名称(例如 Component
)的规则是什么,它们如下:
- 如果有一个点,它是一个成员表达式(
<a.b>
->h(a.b)
),这意味着组件b
是从对象a
中获取的 - 否则,如果名称不是有效的标识符,则它是一个字面量(
<a-b>
->h('a-b')
) - 否则,如果名称以小写字母开头,则它是一个字面量(
<a>
->h('a')
) - 否则,它是一个标识符(
<A>
->h(A)
),这意味着组件A
布局
有一个特殊的组件:布局。如果定义了布局,它将用于包装所有内容。可以使用默认导出从 MDX 中定义布局:
MDX
export default function Layout({children}) {
return <main>{children}</main>;
}
所有的东西。
布局也可以被导入,然后使用 export … from
导出:
MDX
export {Layout as default} from './components.js'
布局也可以作为 components.wrapper
传递(但本地布局优先)。
MDX 提供者
您可能不需要提供者。通常情况下,传递组件就足够了。提供者通常只会增加额外的负担。比如这个文件:
post.mdx
# Hello world
如下使用:
app.jsx
import React from 'react'
import ReactDom from 'react-dom'
import {Heading, /* … */ Table} from './components/index.js'
import Post from './post.mdx' // 假设使用集成编译 MDX -> JS。
const components = {
h1: Heading.H1,
// …
table: Table
}
const root = ReactDom.createRoot(document.getElementById('root'))
root.render(<Post components={components} />)
这样可以工作,这些组件被使用了。
但是当您嵌套 MDX 文件(相互导入它们)时,可能会变得麻烦。就像这样:
post.mdx
import License from './license.md' // 假设使用集成编译 MDX -> JS。
import Contributing from './docs/contributing.mdx'
# Hello world
<License components={props.components} />
---
<Contributing components={props.components} />
为了解决这个问题,在 React、Preact 和 Vue 中可以使用一个 上下文。上下文提供了一种通过组件树传递数据的方法,而不必在每个级别手动传递 props。设置方法如下:
- 根据您使用的框架,安装
@mdx-js/react
、@mdx-js/preact
或@mdx-js/vue
中的一个 - 使用
ProcessorOptions
中的providerImportSource
配置您的 MDX 集成,设置为该包,因此为'@mdx-js/react'
、'@mdx-js/preact'
或'@mdx-js/vue'
- 从该包中导入
MDXProvider
。使用它来包装您的最顶层 MDX 内容组件,并将其传递给您的components
:
Diff
+import {MDXProvider} from '@mdx-js/react'
import React from 'react'
import ReactDom from 'react-dom'
import {Heading, /* … */ Table} from './components/index.js'
import Post from './post.mdx' // 假设使用集成编译 MDX -> JS。
const components = {
h1: Heading.H1,
// …
table: Table
}
const root = ReactDom.createRoot(document.getElementById('root'))
-root.render(<Post components={components} />)
+root.render(
+ <MDXProvider components={components}>
+ <Post />
+ </MDXProvider>
+)
现在,您可以删除显式和冗长的组件传递:
Diff
import License from './license.md' // 假设使用集成编译 MDX -> JS。
import Contributing from './docs/contributing.mdx'
# Hello world
-<License components={props.components} />
+<License />
---
-<Contributing components={props.components} />
+<
Contributing />
当 MDXProvider 嵌套时,它们的组件会合并。看这个例子:
TypeScript
<>
<MDXProvider components={{h1: Component1, h2: Component2}}>
<MDXProvider components={{h2: Component3, h3: Component4}}>
<Content />
</MDXProvider>
</MDXProvider>
</>
…结果是 h1
使用 Component1
,h2
使用 Component3
,h3
使用 Component4
。
要以不同的方式合并或不合并,将函数传递给 components
。它会给出当前上下文 components
,并将其返回的内容用于替代。在此示例中,当前上下文组件被丢弃:
TypeScript
<>
<MDXProvider components={{h1: Component1, h2: Component2}}>
<MDXProvider
components={
function () {
return {h2: Component3, h3: Component4}
}
}
>
<Content />
</MDXProvider>
</MDXProvider>
</>
…结果是 h2
使用 Component3
,h3
使用 Component4
。没有组件用于 h1
。
如果您不是经常嵌套 MDX 文件,或者根本不嵌套它们,请不要使用提供者:显式传递组件。