自定义 React 组件
在 MDX 文件中定义和使用自定义 React 组件,支持服务端渲染、Tailwind 样式以及使用 Jamdesk 内置组件。
直接在 MDX 文件中定义自定义 React 组件。在文件顶部导出箭头函数组件,然后在正文中使用。组件会在构建时进行服务端渲染,并可使用所有 Jamdesk 内置组件和 Tailwind 类。
基本用法
在 MDX 文件顶部使用 export const 定义组件:
export const Highlight = ({ children, color }) => (
<span style={{ backgroundColor: color, padding: '0.2em 0.4em', borderRadius: '4px' }}>
{children}
</span>
);
This text has a <Highlight color="#ffeb3b">yellow highlight</Highlight> in it.
组件名称必须使用 PascalCase(以大写字母开头)。
示例:Hero 卡片
下面是一个包含多个 prop 和 Tailwind 类的复杂示例:
export const HeroCard = ({ title, description, href, icon }) => (
<a
href={href}
className="group block p-6 rounded-lg border border-gray-200 hover:border-blue-500 transition-colors"
>
<div className="flex items-center gap-3 mb-2">
<Icon icon={icon} />
<h3 className="font-semibold text-lg">{title}</h3>
</div>
<p className="text-gray-600">{description}</p>
</a>
);
<div className="grid grid-cols-2 gap-4">
<HeroCard
title="Getting started"
description="Learn the basics"
href="/quickstart"
icon="rocket"
/>
<HeroCard
title="Components"
description="Explore available components"
href="/components/overview"
icon="puzzle-piece"
/>
</div>
使用内置组件
自定义组件可以使用 Jamdesk 的所有内置组件。无需导入:
export const FeatureCard = ({ title, children }) => (
<Card title={title} icon="star">
{children}
<Tip>This tip is inside a custom component!</Tip>
</Card>
);
<FeatureCard title="My Feature">
<Note>Notes work inside custom components too!</Note>
</FeatureCard>
内置组件分类如下:
| 类别 | 组件 |
|---|---|
| 提示框 | Note, Tip, Info, Warning, Check, Danger |
| 布局 | Card, Columns, Tabs, Tab, Accordion, Steps, Step, View |
| 媒体 | Frame, Icon, Badge, Tooltip |
| 代码 | CodeGroup |
| API 文档 | ParamField, ResponseField, Expandable |
限制
- 仅支持箭头函数:使用
export const语法。不支持export function语法。 - 不支持导入:无法导入外部软件包或其他文件。
- 不支持 Hooks:React Hooks(
useState、useEffect、useRef等)不可用。组件会在构建期间进行服务端渲染,而 Hooks 只能在客户端运行。对于交互式组件,请改用代码片段文件(见下方的故障排除)。 - 仅支持服务端渲染:组件在服务端渲染,不支持客户端交互或事件处理程序。
- 仅支持 Tailwind:使用 Tailwind CSS 类进行样式设置,不支持 CSS-in-JS。
- 语法严格:内联组件中的语法错误会导致构建失败,并显示明确的错误消息。
- PascalCase 名称:组件名称必须以大写字母开头,且只能包含字母和数字(不能包含下划线)。
需要使用 React Hooks? 内联组件无法使用 useState、useEffect 或其他 Hooks,因为它们会在构建期间于服务端渲染。对于需要状态或副作用的交互式组件,请创建一个带有 'use client' 指令的代码片段文件(见下方的故障排除部分)。
最佳实践
内联组件应仅负责展示。对于复杂逻辑,请请求使用内置组件。
确保组件使用适当的 HTML 元素和 ARIA 属性,以满足无障碍要求。
在本地预览文档,验证组件在浅色和深色模式下都能正确渲染。
故障排除
错误: Expected component 'MyComponent' to be defined
原因:
- 组件名称未使用 PascalCase(必须以大写字母开头)
- 组件定义中存在语法错误
- 缺少闭合标签或括号
解决方案: 检查导出是否遵循以下模式:
export const MyComponent = ({ prop }) => (
<div>{prop}</div>
);错误: 构建失败,并提示 Babel 或 JSX 语法错误。
原因:
- 组件中的 JSX 语法无效
- 标签或括号未闭合
- 使用了不受支持的 JavaScript 功能
解决方案: 验证 JSX 是否有效。常见问题包括:
- 所有标签都必须闭合(使用
<img loading="lazy" />,而不是<img loading="lazy">) - 使用
className,而不是class - 将多个元素包装在片段
<>...</>或父元素中
警告: Inline component(s) override built-in: Note
原因: 内联组件与内置组件使用了相同的名称。
解决方案: 重命名组件以避免冲突:
// Instead of: export const Note = ...
export const CustomNote = ({ children }) => (
<div className="my-note">{children}</div>
);问题: Tailwind 类无法正常工作。
原因:
- 使用了 CSS-in-JS 或内联样式对象(支持有限)
- Tailwind 类未包含在构建中
解决方案: 使用标准 Tailwind 工具类。对于自定义样式,请使用带有简单值的内联 style prop:
export const Highlight = ({ children }) => (
<span style={{ backgroundColor: '#ffeb3b' }}>{children}</span>
);问题: 使用 export function 定义的组件未渲染。
原因: 仅支持 export const 箭头函数语法。
解决方案: 将函数转换为箭头函数语法:
// Instead of:
export function MyComponent({ prop }) {
return <div>{prop}</div>;
}
// Use:
export const MyComponent = ({ prop }) => (
<div>{prop}</div>
);问题: 包含下划线或特殊字符的组件无法正常工作。
原因: 组件名称必须是有效的 PascalCase 标识符(只能包含字母和数字)。
解决方案: 组件名称中只能使用字母和数字:
// Instead of: export const Hero_Card = ...
// Instead of: export const my-component = ...
export const HeroCard = ({ title }) => (
<div>{title}</div>
);错误: ReferenceError: useState is not defined
原因: 内联组件无法使用 React Hooks。内联组件会在构建过程中进行服务端渲染,而 Hooks 只能在客户端 React 组件中运行。
解决方案: 对于需要状态或副作用的交互式组件,请改为创建代码片段文件:
- 在
/snippets目录中创建文件(例如/snippets/counter.tsx) - 在文件顶部添加
'use client' - 在 MDX 中导入并使用该文件
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}import { Counter } from '/snippets/counter';
<Counter />