使用 MDX
¥Using MDX
本文介绍如何在项目中使用 MDX 文件。它展示了如何传递 props 以及如何导入、定义或传递组件。 请参阅 § 入门 了解如何将 MDX 集成到你的项目中。要了解 MDX 格式的工作原理,我们建议你从 § 什么是 MDX 开始。
¥This article explains how to use MDX files in your project. It shows how you can pass props and how to import, define, or pass components. See § Getting started for how to integrate MDX into your project. To understand how the MDX format works, we recommend that you start with § What is MDX.
内容
¥Contents
MDX 的工作原理
¥How MDX works
集成将 MDX 语法编译为 JavaScript。假设我们有一个 MDX 文档 example.mdx
:
¥An integration compiles MDX syntax to JavaScript. Say we have an MDX document, example.mdx
:
export function Thing() {
return <>World</>
}
# Hello <Thing />
大致变成了下面的 JavaScript。以下内容可能有助于形成心理模型:
¥That’s roughly turned into the following JavaScript. The below might help to form a mental model:
/* @jsxRuntime automatic */
/* @jsxImportSource react */
export function Thing() {
return <>World</>
}
export default function MDXContent() {
return <h1>Hello <Thing /></h1>
}
一些观察结果:
¥Some observations:
输出是序列化的 JavaScript,仍需要评估
¥The output is serialized JavaScript that still needs to be evaluated
注入注释来配置 JSX 的处理方式
¥A comment is injected to configure how JSX is handled
这是一个包含导入/导出的完整文件
¥It’s a complete file with import/exports
导出组件 (
MDXContent
)¥A component (
MDXContent
) is exported
实际输出是:
¥The actual output is:
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)
}
更多观察结果:
¥Some more observations:
JSX 被编译为函数调用和 React 的导入†
¥JSX is compiled away to function calls and an import of React†
内容组件可以给
{components: {wrapper: MyLayout}}
来封装所有内容¥The content component can be given
{components: {wrapper: MyLayout}}
to wrap all content内容组件可以指定
{components: {h1: MyComponent}}
以使用其他内容作为标题¥The content component can be given
{components: {h1: MyComponent}}
to use something else for the heading
† MDX 未与 React 耦合。你还可以将其与 Preact、Vue、Emotion、主题界面 等一起使用。支持经典和自动 JSX 运行时。
¥† MDX is not coupled to React. You can also use it with Preact, Vue, Emotion, Theme UI, etc. Both the classic and automatic JSX runtimes are supported.
MDX 内容
¥MDX content
我们刚刚看到 MDX 文件被编译为组件。你可以像你选择的框架中的任何其他组件一样使用这些组件。获取这个文件:
¥We just saw that MDX files are compiled to components. You can use those components like any other component in your framework of choice. Take this file:
# Hi!
它可以在 React 应用中导入和使用,如下所示:
¥It could be imported and used in a React app like so:
import React from 'react'
import ReactDom from 'react-dom'
import Example from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
const root = ReactDom.createRoot(document.getElementById('root'))
root.render(<Example />)
主要内容作为默认导出导出。所有其他值也会被导出。举个例子:
¥The main content is exported as the default export. All other values are also exported. Take this example:
export function Thing() {
return <>World</>
}
# Hello <Thing />
可以通过以下方式导入:
¥It could be imported in the following ways:
// A namespace import to get everything:
import * as everything from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
console.log(everything) // {Thing: [Function: Thing], default: [Function: MDXContent]}
// Default export shortcut and a named import specifier:
import Content, {Thing} from './example.mdx'
console.log(Content) // [Function: MDXContent]
console.log(Thing) // [Function: Thing]
// Import specifier with another local name:
import {Thing as AnotherName} from './example.mdx'
console.log(AnotherName) // [Function: Thing]
属性
¥Props
在 § 什么是 MDX 中,我们展示了大括号内的 JavaScript 表达式可以在 MDX 中使用:
¥In § What is MDX, we showed that JavaScript expressions, inside curly braces, can be used in MDX:
import {year} from './data.js'
export const name = 'world'
# Hello {name.toUpperCase()}
The current year is {year}
数据也可以传递到 MDXContent
,而不是在 MDX 中导入或定义数据。传递的数据称为 props
。举个例子:
¥Instead of importing or defining data within MDX, data can also be passed to MDXContent
. The passed data is called props
. Take for example:
# Hello {props.name.toUpperCase()}
The current year is {props.year}
该文件可以用作:
¥This file could be used as:
import Example from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
// Use a `createElement` call:
React.createElement(Example, {name: 'Venus', year: 2021})
// Use JSX:
<Example name="Mars" year={2022} />
组件列表
¥Components
有一个特殊的属性:components
。它需要一个将组件名称映射到组件的对象。举个例子:
¥There is one special prop: components
. It takes an object mapping component names to components. Take this example:
# Hello *<Planet />*
它可以从 JavaScript 导入并传递组件,如下所示:
¥It can be imported from JavaScript and passed components like so:
import Example from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
<Example
components={{
Planet() {
return <span style={{color: 'tomato'}}>Pluto</span>
}
}}
/>
你不必传递组件。你还可以在 MDX 中定义或导入它们:
¥You don’t have to pass components. You can also define or import them within MDX:
import {Box, Heading} from 'rebass'
MDX using imported components!
<Box>
<Heading>Here’s a heading</Heading>
</Box>
由于 MDX 文件是组件,因此它们也可以相互导入:
¥Because MDX files are components, they can also import each other:
import License from './license.md' // Assumes an integration is used to compile MDX -> JS.
import Contributing from './docs/contributing.mdx'
# Hello world
<License />
---
<Contributing />
以下是传递组件的其他一些示例:
¥Here are some other examples of passing components:
<Example
components={{
// Map `h1` (`# heading`) to use `h2`s.
h1: 'h2',
// Rewrite `em`s (`*like so*`) to `i` with a goldenrod foreground color.
em(props) {
return <i style={{color: 'goldenrod'}} {...props} />
},
// Pass a layout (using the special `'wrapper'` key).
wrapper({components, ...rest}) {
return <main {...rest} />
},
// Pass a component.
Planet() {
return 'Neptune'
},
// This nested component can be used as `<theme.text>hi</theme.text>`
theme: {
text(props) {
return <span style={{color: 'grey'}} {...props} />
}
}
}}
/>
可以在 components
中传递以下键:
¥The following keys can be passed in components
:
使用 Markdown 编写的内容的 HTML 等效项,例如
h1
对应# heading
(有关示例,请参阅 § 组件表)¥HTML equivalents for the things you write with markdown such as
h1
for# heading
(see § Table of components for examples)wrapper
,定义布局(但本地布局优先)¥
wrapper
, which defines the layout (but a local layout takes precedence)*
对于你使用 JSX 编写的内容来说,任何其他有效的 JavaScript 标识符(foo
、Quote
、_
、$x
、a1
)(例如<So />
或<like.so />
,请注意本地定义的组件优先)‡¥
*
anything else that is a valid JavaScript identifier (foo
,Quote
,_
,$x
,a1
) for the things you write with JSX (like<So />
or<like.so />
, note that locally defined components take precedence)‡
如果你想知道 JSX 中的名称(例如 <x>
中的 x
)是否是字面标签名称(例如 h1
)或不是(例如 Component
)的规则是什么,它们如下:
¥If you ever wondered what the rules are for whether a name in JSX (so x
in <x>
) is a literal tag name (like h1
) or not (like Component
), they are as follows:
如果有点,则为成员表达式(
<a.b>
->h(a.b)
),这意味着b
组件取自对象a
¥If there’s a dot, it’s a member expression (
<a.b>
->h(a.b)
), which means that theb
component is taken from objecta
否则,如果名称不是有效标识符,则它是字面量 (
<a-b>
->h('a-b')
)¥Otherwise, if the name is not a valid identifier, it’s a literal (
<a-b>
->h('a-b')
)否则,如果它以小写字母开头,则它是一个字面量 (
<a>
->h('a')
)¥Otherwise, if it starts with a lowercase, it’s a literal (
<a>
->h('a')
)否则,它是一个标识符(
<A>
->h(A)
),这意味着组件A
¥Otherwise, it’s an identifier (
<A>
->h(A)
), which means a componentA
布局
¥Layout
有一个特殊的组件:布局。如果已定义,则用于封装所有内容。可以使用默认导出从 MDX 中定义布局:
¥There is one special component: the layout. If it is defined, it’s used to wrap all content. A layout can be defined from within MDX using a default export:
export default function Layout({children}) {
return <main>{children}</main>;
}
All the things.
布局也可以使用 export … from
导入然后导出:
¥The layout can also be imported and then exported with an export … from
:
export {Layout as default} from './components.js'
布局也可以作为 components.wrapper
传递(但本地布局优先)。
¥The layout can also be passed as components.wrapper
(but a local one takes precedence).
MDX 提供商
¥MDX provider
你可能不需要提供者。传递组件通常没问题。提供商通常只会增加额外的重量。以这个文件为例:
¥You probably don’t need a provider. Passing components is typically fine. Providers often only add extra weight. Take for example this file:
# Hello world
像这样使用:
¥Used like so:
import React from 'react'
import ReactDom from 'react-dom'
import {Heading, /* … */ Table} from './components/index.js'
import Post from './post.mdx' // Assumes an integration is used to compile MDX -> JS.
const components = {
h1: Heading.H1,
// …
table: Table
}
const root = ReactDom.createRoot(document.getElementById('root'))
root.render(<Post components={components} />)
有效,这些组件已被使用。
¥That works, those components are used.
但是,当你嵌套 MDX 文件(将它们相互导入)时,它可能会变得很麻烦。就像这样:
¥But when you’re nesting MDX files (importing them into each other) it can become cumbersome. Like so:
import License from './license.md' // Assumes an integration is used to compile MDX -> JS.
import Contributing from './docs/contributing.mdx'
# Hello world
<License components={props.components} />
---
<Contributing components={props.components} />
为了解决这个问题,可以在 React、Preact 和 Vue 中使用 context。Context 提供了一种通过组件树传递数据的方法,而无需在每个级别手动向下传递 props。像这样设置:
¥To solve this, a context can be used in React, Preact, and Vue. Context provides a way to pass data through the component tree without having to pass props down manually at every level. Set it up like so:
安装
@mdx-js/react
、@mdx-js/preact
或@mdx-js/vue
,具体取决于你使用的框架¥Install either
@mdx-js/react
,@mdx-js/preact
, or@mdx-js/vue
, depending on what framework you’re using配置 MDX 集成,并将
providerImportSource
于ProcessorOptions
设置为该包,因此'@mdx-js/react'
、'@mdx-js/preact'
或'@mdx-js/vue'
¥Configure your MDX integration with
providerImportSource
inProcessorOptions
set to that package, so either'@mdx-js/react'
,'@mdx-js/preact'
, or'@mdx-js/vue'
从该包导入
MDXProvider
。使用它来封装最顶层的 MDX 内容组件,并将其传递给你的components
:¥Import
MDXProvider
from that package. Use it to wrap your top-most MDX content component and pass it yourcomponents
instead:
+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' // Assumes an integration is used to compile 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>
+)
现在你可以删除显式且详细的组件传递:
¥Now you can remove the explicit and verbose component passing:
import License from './license.md' // Assumes an integration is used to compile MDX -> JS.
import Contributing from './docs/contributing.mdx'
# Hello world
-<License components={props.components} />
+<License />
---
-<Contributing components={props.components} />
+<Contributing />
当 MDXProvider
嵌套时,它们的组件会合并。举个例子:
¥When MDXProvider
s are nested, their components are merged. Take this example:
<>
<MDXProvider components={{h1: Component1, h2: Component2}}>
<MDXProvider components={{h2: Component3, h3: Component4}}>
<Content />
</MDXProvider>
</MDXProvider>
</>
…这导致 h1
使用 Component1
,h2
使用 Component3
,h3
使用 Component4
。
¥…which results in h1
s using Component1
, h2
s using Component3
, and h3
s using Component4
.
要以不同方式合并或根本不合并,请将函数传递给 components
。它给出了当前上下文 components
,并且将使用它返回的内容。在此示例中,当前上下文组件被丢弃:
¥To merge differently or not at all, pass a function to components
. It’s given the current context components
and what it returns will be used instead. In this example the current context components are discarded:
<>
<MDXProvider components={{h1: Component1, h2: Component2}}>
<MDXProvider
components={
function () {
return {h2: Component3, h3: Component4}
}
}
>
<Content />
</MDXProvider>
</MDXProvider>
</>
…这导致 h2
使用 Component3
,h3
使用 Component4
。h1
未使用任何组件。
¥…which results in h2
s using Component3
and h3
s using Component4
. No component is used for h1
.
如果你不嵌套 MDX 文件,或者不经常嵌套它们,请不要使用提供程序:显式传递组件。
¥If you’re not nesting MDX files, or not nesting them often, don’t use providers: pass components explicitly.