MDX 初探:让 Markdown 拥有组件的力量
前端
- # astro
- # mdx
- # markdown
背景
在写上一篇在 Astro 中嵌入 Excalidraw 交互画布时,我第一次接触了 MDX——文章后缀从 .md 变成了 .mdx。当时只是照着文档把组件嵌入进去,没细想背后的原理。这篇文章记录一下对 MDX 的初步了解。
MDX 是什么
MDX = Markdown + JSX。
普通 Markdown 能写标题、列表、代码块这些格式化内容,但一旦需要嵌入交互组件(比如一个按钮、一个图表、一个画板),Markdown 就无能为力了。
MDX 在 Markdown 的基础上加入了 JSX 语法,让你可以在文章中直接导入和使用 React 组件:
import MyChart from '../components/MyChart.tsx'
## 数据趋势
下面是本站访问量的变化趋势:
<MyChart data={visitData} />
就这么简单——import 组件,像写 JSX 一样使用它。
为什么需要 MDX
普通 Markdown 的局限
Markdown 的设计目标是纯文本写作,它擅长格式化内容,但不擅长:
- 嵌入交互式组件(按钮、表单、图表)
- 复用 UI 组件(一个「提示框」要在多篇文章里重复写 HTML)
- 传递动态数据
MDX 解决了什么
| 能力 | Markdown | MDX |
|---|---|---|
| 基础格式(标题、列表、代码块) | ✅ | ✅ |
| 嵌入 React/Vue 组件 | ❌ | ✅ |
| 使用 props 传参 | ❌ | ✅ |
| import/export 语句 | ❌ | ✅ |
| 条件渲染 | ❌ | ✅ |
| 与设计系统整合 | ❌ | ✅ |
简单说:MDX 让 Markdown 从「静态文档」变成了「可编程页面」。
语法差异
MDX 和 Markdown 绠一的部分完全一样,区别在于可以写 JSX:
导入组件
import Button from '../components/Button.astro'
使用组件
<Button>点我</Button>
传递 Props
<Counter initial={42} />
导出变量
MDX 文件可以 export 变量,供组件使用:
export const data = [1, 2, 3]
<Chart values={data} />
JSX 表达式
可以在花括号中使用 JavaScript 表达式:
今年是 {new Date().getFullYear()} 年
{2 * 21 === 42 && <span>计算正确</span>}
在 Astro 中使用 MDX
Astro 通过 @astrojs/mdx 集成支持 MDX。本项目的配置:
// astro.config.ts
import mdx from '@astrojs/mdx'
export default defineConfig({
integrations: [mdx()],
})
内容集合(Content Collections)中,.md 和 .mdx 文件会被同等对待:
// src/content.config.ts
const posts = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/posts' }),
})
只需将文章文件从 .md 改为 .mdx,就可以在文章中使用组件了。不需要改任何路由或构建配置。
实际案例
上一篇 Excalidraw 文章就是典型的 MDX 用法:
---
title: '在 Astro 中嵌入 Excalidraw 交互画布'
---
import ExcalidrawWrapper from '@hokkeung/astro-excalidraw-editable'
import demoData from '../../excalidraw/demo.excalidraw.json'
## 效果展示
<ExcalidrawWrapper initialData={demoData} dataPath="src/content/excalidraw/demo.excalidraw.json" />
这段代码做了三件事:
- 导入 Astro 组件:
ExcalidrawWrapper是一个封装了 React Excalidraw 的 Astro 组件 - 导入 JSON 数据:画布数据直接作为静态资源导入
- 将数据传入组件:通过
initialDataprop 传递
如果只用普通 Markdown,这些都不可能做到——最多贴一张 Excalidraw 的导出截图。
注意事项
- 不要滥用:如果文章只是纯文字内容,用
.md就够了。MDX 带来了灵活性,但也增加了复杂度 - SSR 兼容性:某些组件(如 Excalidraw)不能在 SSR 环境运行,需要使用
client:only指令 - 构建速度:MDX 需要额外的编译步骤,大量 MDX 文件可能略微影响构建速度
- 语法冲突:MDX 中 JSX 语法和 Markdown 语法偶尔会冲突(比如
<可能被当作 HTML 标签解析),需要用空行或{/* */}注释来分隔