MDX 及 React 组件
MDX 将 Markdown 的简单性与 React 组件的强大功能相结合,让您能够创建交互式、动态的文档。本章探讨如何在 Rspress 中利用 MDX 构建引人入胜的文档体验。
什么是 MDX?
MDX 概述
MDX 是一种格式,允许您直接在 Markdown 文件中编写 JSX。您可以在传统 Markdown 语法旁边导入和使用 React 组件。
mdx
# 普通 Markdown 标题
这是普通的 Markdown 文本。
<CustomButton onClick={() => alert('你好!')}>
点击我
</CustomButton>
## 更多 Markdown
继续编写 Markdown...为什么使用 MDX?
- 交互示例: 嵌入实时代码演示
- 自定义组件: 创建可重用的文档元素
- 动态内容: 以编程方式生成内容
- 丰富可视化: 添加图表、图表和交互式媒体
- 增强用户体验: 构建更好的用户体验
基本 MDX 语法
导入组件
直接在 MDX 中导入 React 组件:
mdx
import { Alert } from '../components/Alert';
import { CodeBlock } from '../components/CodeBlock';
# 我的文档
<Alert type="info">
这是一条信息消息!
</Alert>
<CodeBlock language="javascript">
console.log('Hello World');
</CodeBlock>内联 JSX
无需导入即可编写内联 JSX:
mdx
# 交互按钮
<button
onClick={() => alert('已点击!')}
style={{ padding: '10px 20px', background: '#007bff', color: 'white' }}
>
点击我
</button>JavaScript 表达式
在内容中使用 JavaScript 表达式:
mdx
export const name = 'Rspress';
export const version = '1.0.0';
# 欢迎使用 {name}
当前版本: **{version}**
发布年份: {new Date().getFullYear()}创建自定义组件
简单组件
创建可重用组件:
tsx
// components/Highlight.tsx
import React from 'react';
interface HighlightProps {
children: React.ReactNode;
color?: string;
}
export function Highlight({ children, color = 'yellow' }: HighlightProps) {
return (
<span
style={{
backgroundColor: color,
padding: '2px 6px',
borderRadius: '3px',
}}
>
{children}
</span>
);
}在 MDX 中使用:
mdx
import { Highlight } from '../components/Highlight';
# 文档
这是 <Highlight>重要文本</Highlight> 需要记住。
这也是 <Highlight color="lightblue">重要的</Highlight>。交互组件
创建交互演示组件:
tsx
// components/Counter.tsx
import React, { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<div style={{ padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}>
<h3>计数器演示</h3>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<button onClick={() => setCount(count - 1)}>减少</button>
<button onClick={() => setCount(0)}>重置</button>
</div>
);
}mdx
import { Counter } from '../components/Counter';
# 交互示例
试试这个计数器:
<Counter />
计数器维护状态并响应用户交互。标签组件
构建用于多个示例的标签组件:
tsx
// components/Tabs.tsx
import React, { useState } from 'react';
interface TabsProps {
children: React.ReactElement[];
}
interface TabProps {
label: string;
children: React.ReactNode;
}
export function Tabs({ children }: TabsProps) {
const [activeTab, setActiveTab] = useState(0);
return (
<div className="tabs-container">
<div className="tab-buttons">
{React.Children.map(children, (child, index) => (
<button
key={index}
onClick={() => setActiveTab(index)}
className={activeTab === index ? 'active' : ''}
>
{child.props.label}
</button>
))}
</div>
<div className="tab-content">
{children[activeTab]}
</div>
</div>
);
}
export function Tab({ children }: TabProps) {
return <div>{children}</div>;
}mdx
import { Tabs, Tab } from '../components/Tabs';
# 安装
<Tabs>
<Tab label="npm">
```bash
npm install rspress
```
</Tab>
<Tab label="yarn">
```bash
yarn add rspress
```
</Tab>
<Tab label="pnpm">
```bash
pnpm add rspress
```
</Tab>
</Tabs>内置组件
提示框
Rspress 提供内置的提示组件:
mdx
::: tip
这是给用户的有用提示。
:::
::: info
这提供了额外的信息。
:::
::: warning
这警告用户潜在的问题。
:::
::: danger
这向用户警示关键信息。
:::代码块
带语法高亮的增强代码块:
mdx
```javascript title="app.js" {2,4-6}
function greet(name) {
console.log(`你好, ${name}!`); // 高亮
// 这些行被高亮
if (!name) {
return '你好, 世界!';
}
return `你好, ${name}!`;
}
```高级模式
条件渲染
有条件地显示内容:
mdx
export const isProduction = process.env.NODE_ENV === 'production';
# 文档
{isProduction ? (
<div>检测到生产环境。</div>
) : (
<div>开发环境 - 某些功能可能表现不同。</div>
)}循环数据
从数组生成内容:
mdx
export const features = [
{ name: '快速', icon: '⚡' },
{ name: '简单', icon: '✨' },
{ name: '强大', icon: '💪' },
];
# 特性
<div style={{ display: 'flex', gap: '20px' }}>
{features.map(feature => (
<div key={feature.name} style={{ textAlign: 'center' }}>
<div style={{ fontSize: '3em' }}>{feature.icon}</div>
<div>{feature.name}</div>
</div>
))}
</div>组件组合
组合多个组件:
mdx
import { Card } from '../components/Card';
import { Button } from '../components/Button';
import { Badge } from '../components/Badge';
# API 概览
<Card>
<Card.Header>
<h3>用户 API <Badge>v2.0</Badge></h3>
</Card.Header>
<Card.Body>
使用我们的 REST API 创建和管理用户帐户。
</Card.Body>
<Card.Footer>
<Button href="/api/users">查看文档</Button>
</Card.Footer>
</Card>代码演示场
实时代码编辑器
创建交互式代码演示场:
tsx
// components/CodePlayground.tsx
import React, { useState } from 'react';
export function CodePlayground({ initialCode }: { initialCode: string }) {
const [code, setCode] = useState(initialCode);
const [output, setOutput] = useState('');
const runCode = () => {
try {
// 警告: 在生产中使用 eval 不安全
// 这仅用于演示
const result = eval(code);
setOutput(String(result));
} catch (error) {
setOutput(`错误: ${error.message}`);
}
};
return (
<div className="code-playground">
<textarea
value={code}
onChange={(e) => setCode(e.target.value)}
rows={10}
style={{ width: '100%', fontFamily: 'monospace' }}
/>
<button onClick={runCode}>运行代码</button>
<div className="output">
<strong>输出:</strong>
<pre>{output}</pre>
</div>
</div>
);
}TypeScript 支持
类型化组件
使用 TypeScript 实现类型安全:
tsx
// components/Card.tsx
import React from 'react';
interface CardProps {
title: string;
description: string;
variant?: 'default' | 'highlighted';
children?: React.ReactNode;
}
export function Card({
title,
description,
variant = 'default',
children
}: CardProps) {
return (
<div className={`card card-${variant}`}>
<h3>{title}</h3>
<p>{description}</p>
{children}
</div>
);
}样式组件
内联样式
mdx
<div style={{
padding: '20px',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
borderRadius: '8px',
}}>
样式化内容
</div>CSS Modules
tsx
// components/Card.module.css
.card {
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
margin: 20px 0;
}
.cardHighlighted {
border-color: #007bff;
background: #f0f8ff;
}tsx
// components/Card.tsx
import styles from './Card.module.css';
export function Card({ highlighted, children }) {
return (
<div className={highlighted ? styles.cardHighlighted : styles.card}>
{children}
</div>
);
}最佳实践
组件组织
components/
├── common/
│ ├── Button.tsx
│ ├── Card.tsx
│ └── Badge.tsx
├── interactive/
│ ├── CodePlayground.tsx
│ ├── ApiDemo.tsx
│ └── Counter.tsx
└── layout/
├── Tabs.tsx
├── Grid.tsx
└── Columns.tsx性能
- 懒加载: 按需加载重型组件
tsx
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
export function Demo() {
return (
<Suspense fallback={<div>加载中...</div>}>
<HeavyComponent />
</Suspense>
);
}- 记忆化: 防止不必要的重新渲染
tsx
import { memo } from 'react';
export const Card = memo(function Card({ title, content }) {
return (
<div>
<h3>{title}</h3>
<p>{content}</p>
</div>
);
});故障排除
常见问题
- 导入错误
Error: Cannot find module '../components/Button'解决方案: 检查文件路径和扩展名
- JSX 未渲染
mdx
<!-- 不起作用 - 需要是有效的 JSX -->
<Button>点击我<Button>
<!-- 正确 -->
<Button>点击我</Button>下一步
最佳实践
- 保持组件小而专注
- 使用 TypeScript 以获得更好的开发体验
- 彻底测试交互组件
- 清楚地记录组件属性
安全性
- 永远不要在生产中对用户输入使用
eval() - 清理任何动态内容
- 小心使用
dangerouslySetInnerHTML - 验证所有外部数据