MDX 完整语法指南
MDX 是 Markdown 的超集,它允许你在 Markdown 文档中直接使用 JSX 组件。这意味着你可以在写作时享受 Markdown 的简洁语法,同时又能利用 React/Preact/Vue 等框架组件的强大功能。本指南将全面介绍 MDX 的各种语法和在 Astro 中的使用方法。
什么是 MDX
MDX 简介
MDX(Markdown + JSX)是一种将 Markdown 和 JSX 结合的文件格式。它由 Unified 团队开发和维护,已经成为现代文档和博客系统的流行选择。
MDX 的核心理念是:
让内容创作者能够在熟悉的 Markdown 环境中使用组件化的交互式内容。
MDX 与 Markdown 的区别
| 特性 | Markdown | MDX |
|---|---|---|
| 基础文本格式 | ✅ | ✅ |
| 代码块语法高亮 | ✅ | ✅ |
| 表格支持 | ✅ | ✅ |
| 导入组件 | ❌ | ✅ |
| 使用 JSX | ❌ | ✅ |
| JavaScript 表达式 | ❌ | ✅ |
| 自定义组件替换 | ❌ | ✅ |
| 交互式内容 | ❌ | ✅ |
MDX 的优势
- 组件复用:可以创建可复用的 UI 组件,在多篇文章中使用
- 交互性:支持创建交互式的演示、图表、代码示例等
- 类型安全:配合 TypeScript 可以获得完整的类型检查
- 灵活性:可以混合使用 Markdown 和 JSX,根据需要选择最合适的方式
- 生态系统:可以使用任何 React/Preact/Vue 组件库
基础语法
在 MDX 中使用 Markdown
MDX 完全兼容标准 Markdown 语法,你可以像往常一样编写:
# 这是标题
这是一个段落,包含 **粗体** 和 _斜体_ 文本。
- 列表项 1- 列表项 2- 列表项 3
> 这是一段引用所有标准的 Markdown 语法在 MDX 中都能正常工作。
在 MDX 中使用 JSX
MDX 的核心特性是可以直接在文档中使用 JSX 语法:
{ /* 这是 JSX 注释 */}
;<div style={{ padding: '1rem', backgroundColor: '#f0f0f0', borderRadius: '8px' }}> <h3>这是一个 JSX 块</h3> <p>你可以在这里使用任何有效的 JSX 语法。</p></div>注意事项:
- JSX 中的
class属性需要写成className - 内联样式需要使用对象语法:
style={{ color: 'red' }} - JSX 注释使用
{/* 注释内容 */}格式
导入和使用组件
MDX 允许你在文件顶部导入组件,然后在文档中使用:
import MyButton from '../components/MyButton.astro';import { Card, CardHeader, CardBody } from '../components/Card';
# 我的文章
这是一些普通的 Markdown 文本。
<MyButton>点击我</MyButton>
<Card> <CardHeader>卡片标题</CardHeader> <CardBody> 这是卡片的内容,可以包含任何 Markdown 或 JSX。 </CardBody></Card>内联表达式
你可以在 MDX 中使用花括号 {} 来嵌入 JavaScript 表达式:
export const name = "MDX";export const year = 2024;
# 欢迎使用 {name}
当前年份是 {year},{name} 已经发展了 {year - 2018} 年。
今天的日期是:{new Date().toLocaleDateString('zh-CN')}支持的表达式类型:
- 变量引用:
{variableName} - 数学运算:
{1 + 2 + 3} - 字符串操作:
{name.toUpperCase()} - 三元表达式:
{condition ? 'yes' : 'no'} - 函数调用:
{formatDate(date)} - 数组方法:
{items.map(item => item.name).join(', ')}
高级用法
导出变量和函数
你可以在 MDX 文件中导出变量、函数和组件:
export const metadata = { author: "张三", readTime: "5 分钟"};
export function Highlight({ children, color = 'yellow' }) { return ( <span style={{ backgroundColor: color, padding: '0.2em 0.4em', borderRadius: '4px' }}> {children} </span> );}
# 文章标题
作者:{metadata.author} | 阅读时间:{metadata.readTime}
这是一段包含 <Highlight>高亮文本</Highlight> 的内容。
你也可以使用不同的颜色:<Highlight color="#90EE90">绿色高亮</Highlight>自定义组件映射
MDX 允许你用自定义组件替换默认的 HTML 元素。这在 Astro 中通过 components prop 实现:
// 在布局文件中import { Content } from './my-post.mdx'
const components = { // 替换默认的 h1 元素 h1: (props) => <h1 className="custom-heading" {...props} />,
// 替换默认的 a 元素 a: (props) => { const isExternal = props.href?.startsWith('http') return ( <a {...props} target={isExternal ? '_blank' : undefined} rel={isExternal ? 'noopener noreferrer' : undefined} /> ) },
// 替换默认的 code 元素 code: (props) => <code className="inline-code" {...props} />,
// 替换默认的 pre 元素(代码块) pre: (props) => <pre className="code-block" {...props} />,}
;<Content components={components} />可替换的元素列表:
| 元素 | Markdown 语法 | 说明 |
|---|---|---|
h1 - h6 | # 到 ###### | 标题 |
p | 普通段落 | 段落 |
a | [text](url) | 链接 |
img |  | 图片 |
blockquote | > | 引用块 |
ul / ol | - 或 1. | 列表 |
li | 列表项 | 列表项 |
code | `code` | 行内代码 |
pre | ``` | 代码块 |
table | | 语法 | 表格 |
hr | --- | 分割线 |
布局组件
你可以为 MDX 内容指定布局组件:
// my-post.mdxexport { default as Layout } from '../layouts/PostLayout.astro';
# 文章标题
文章内容...或者在 Astro 中通过配置指定默认布局:
import { defineConfig } from 'astro/config'import mdx from '@astrojs/mdx'
export default defineConfig({ integrations: [ mdx({ // MDX 配置选项 }), ],})传递 Props
组件可以接收和使用 props:
export function Alert({ type = 'info', title, children }) { const colors = { info: { bg: '#e3f2fd', border: '#2196f3' }, warning: { bg: '#fff3e0', border: '#ff9800' }, error: { bg: '#ffebee', border: '#f44336' }, success: { bg: '#e8f5e9', border: '#4caf50' } };
const style = { padding: '1rem', borderLeft: `4px solid ${colors[type].border}`, backgroundColor: colors[type].bg, borderRadius: '4px', marginBottom: '1rem' };
return ( <div style={style}> {title && <strong>{title}</strong>} <div>{children}</div> </div> );}
<Alert type="info" title="提示"> 这是一条信息提示。</Alert>
<Alert type="warning" title="警告"> 请注意这个重要事项!</Alert>
<Alert type="error" title="错误"> 操作失败,请重试。</Alert>
<Alert type="success" title="成功"> 操作已成功完成!</Alert>在 Astro 中使用 MDX
配置说明
要在 Astro 项目中使用 MDX,首先需要安装和配置 MDX 集成:
# 安装 MDX 集成npm install @astrojs/mdx
# 或使用 pnpmpnpm add @astrojs/mdx
# 或使用 yarnyarn add @astrojs/mdx然后在 astro.config.mjs 中添加配置:
import { defineConfig } from 'astro/config'import mdx from '@astrojs/mdx'
export default defineConfig({ integrations: [mdx()], markdown: { // Markdown 配置也会应用到 MDX shikiConfig: { theme: 'github-dark', wrap: true, }, },})MDX 配置选项
import { defineConfig } from 'astro/config'import mdx from '@astrojs/mdx'import remarkGfm from 'remark-gfm'import rehypeSlug from 'rehype-slug'
export default defineConfig({ integrations: [ mdx({ // 语法扩展 syntaxHighlight: 'shiki', // 或 'prism'
// Remark 插件(处理 Markdown) remarkPlugins: [remarkGfm],
// Rehype 插件(处理 HTML) rehypePlugins: [rehypeSlug],
// 是否使用 Astro 的 Markdown 配置 extendMarkdownConfig: true,
// 优化选项 optimize: true, }), ],})在 Astro 组件中使用 MDX
---import { getCollection } from 'astro:content'import PostLayout from '../../layouts/PostLayout.astro'
export async function getStaticPaths() { const posts = await getCollection('posts') return posts.map((post) => ({ params: { slug: post.slug }, props: { post }, }))}
const { post } = Astro.propsconst { Content } = await post.render()---
<PostLayout frontmatter={post.data}> <Content /></PostLayout>导入 Astro 组件到 MDX
你可以在 MDX 文件中导入和使用 Astro 组件:
---title: "使用 Astro 组件的文章"---
import Button from '../../components/common/Button.astro';import Container from '../../components/common/Container.astro';
# 我的文章
<Container> 这是容器内的内容。
<Button>点击这里</Button></Container>注意:Astro 组件在 MDX 中是静态渲染的,不支持客户端交互。如果需要交互性,请使用带有 client:* 指令的框架组件。
使用客户端组件
对于需要客户端交互的组件,可以使用 React/Vue/Svelte 等框架组件:
import Counter from '../../components/Counter.jsx';
# 交互式计数器示例
下面是一个可交互的计数器组件:
<Counter client:load />
使用 `client:load` 指令使组件在页面加载时立即水合。客户端指令说明:
| 指令 | 说明 |
|---|---|
client:load | 页面加载时立即水合 |
client:idle | 页面空闲时水合 |
client:visible | 组件进入视口时水合 |
client:media | 满足媒体查询条件时水合 |
client:only | 仅在客户端渲染,跳过 SSR |
最佳实践
1. 文件组织
推荐的文件组织结构:
src/├── content/│ └── posts/│ ├── my-post.mdx│ └── another-post.mdx├── components/│ ├── mdx/ # MDX 专用组件│ │ ├── Alert.astro│ │ ├── CodeDemo.astro│ │ └── Tabs.astro│ └── common/ # 通用组件│ ├── Button.astro│ └── Card.astro└── layouts/ └── PostLayout.astro2. 组件设计原则
- 保持简单:MDX 组件应该专注于内容展示
- 可复用性:设计通用的组件,避免过度定制
- 无障碍性:确保组件符合 WCAG 标准
- 性能优先:避免在 MDX 中使用过重的组件
3. 性能优化
// 使用动态导入减少初始包大小const HeavyComponent = await import('../components/HeavyComponent')
// 使用 client:visible 延迟加载;<HeavyComponent client:visible />4. 类型安全
为 MDX 组件添加 TypeScript 类型:
interface AlertProps { type?: 'info' | 'warning' | 'error' | 'success' title?: string children: React.ReactNode}
export function Alert({ type = 'info', title, children }: AlertProps) { // 组件实现}常见问题
Q: MDX 文件和 Markdown 文件有什么区别?
A: MDX 文件(.mdx)支持导入组件和使用 JSX 语法,而 Markdown 文件(.md)只支持标准 Markdown 语法。在 Astro 中,两者都可以用于内容集合,但 MDX 提供更多的灵活性。
Q: 为什么我的组件没有正确渲染?
A: 常见原因包括:
- 组件路径错误
- 忘记添加
client:*指令(对于交互式组件) - JSX 语法错误(如使用
class而不是className) - 组件没有正确导出
Q: 如何在 MDX 中使用条件渲染?
A: 使用 JSX 的条件渲染语法:
export const showAdvanced = true;
# 文档
基础内容...
{showAdvanced && ( <div> ## 高级内容
这部分只在 showAdvanced 为 true 时显示。 </div>)}Q: MDX 支持 frontmatter 吗?
A: 是的,MDX 完全支持 YAML frontmatter:
---title: '我的文章'description: '文章描述'pubDate: 2024-01-15---Q: 如何处理 MDX 中的转义字符?
A: 在 MDX 中,某些字符需要转义:
{和}需要写成\{和\}或使用{'{'}<在某些情况下需要写成<
// 显示花括号这是一个对象:\{key: value\}
// 或者这是一个对象:{'{'}key: value{'}'}Q: 如何在 MDX 中添加自定义样式?
A: 有几种方式:
// 1. 内联样式<div style={{ color: 'red', fontSize: '1.2em' }}> 红色文本</div>
// 2. CSS 类名<div className="custom-class"> 使用 CSS 类</div>
// 3. 导入 CSS 模块import styles from './styles.module.css';
<div className={styles.container}> 使用 CSS 模块</div>实用示例
创建可折叠内容
export function Collapsible({ title, children }) { return ( <details style={{ marginBottom: '1rem' }}> <summary style={{ cursor: 'pointer', fontWeight: 'bold' }}> {title} </summary> <div style={{ paddingTop: '0.5rem' }}>{children}</div> </details> )}
;<Collapsible title="点击展开更多内容"> 这里是隐藏的内容,点击标题可以展开或收起。 - 支持 Markdown 语法 - 支持嵌套组件 - 支持任意内容</Collapsible>创建标签页组件
export function Tabs({ children, labels }) { return ( <div className="tabs-container"> <div className="tabs-header"> {labels.map((label, index) => ( <button key={index} className="tab-button"> {label} </button> ))} </div> <div className="tabs-content">{children}</div> </div> )}使用示例:
<Tabs labels={['JavaScript', 'Python', 'Go']}> {/* JavaScript 代码 */} <pre> <code>console.log('Hello, World!');</code> </pre>
{/* Python 代码 */} <pre> <code>print('Hello, World!')</code> </pre>
{/* Go 代码 */} <pre> <code>fmt.Println("Hello, World!")</code> </pre></Tabs>创建代码对比组件
export function CodeComparison({ before, after, language = 'javascript' }) { return ( <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }} > <div> <h4 style={{ color: '#ef4444' }}>❌ 之前</h4> <pre> <code className={`language-${language}`}>{before}</code> </pre> </div> <div> <h4 style={{ color: '#22c55e' }}>✅ 之后</h4> <pre> <code className={`language-${language}`}>{after}</code> </pre> </div> </div> )}总结
MDX 是一个强大的工具,它将 Markdown 的简洁性与组件化开发的灵活性完美结合。通过本指南,你已经了解了:
- MDX 基础:什么是 MDX,以及它与 Markdown 的区别
- 基础语法:如何在 MDX 中使用 JSX、导入组件和使用表达式
- 高级用法:自定义组件、布局组件和 props 传递
- Astro 集成:如何在 Astro 项目中配置和使用 MDX
- 最佳实践:文件组织、性能优化和类型安全
- 常见问题:解决使用 MDX 时可能遇到的问题
掌握 MDX 后,你可以创建更加丰富、交互性更强的内容,同时保持 Markdown 的写作体验。
参考资源
- MDX 官方文档 - MDX 官方文档和 API 参考
- Astro MDX 集成 - Astro 官方 MDX 集成指南
- Unified 生态系统 - MDX 底层的处理引擎
- Remark 插件列表 - Markdown 处理插件
- Rehype 插件列表 - HTML 处理插件
本文最后更新于 2024 年 1 月 15 日