在MDX里嵌入交互组件
MDX 让你在 Markdown 正文里直接使用 React 组件。这篇讲清 client:* 指令为什么不能省、在 Astro 里用 shadcn/ui 会踩的三个坑,以及什么时候压根不该用 MDX。
普通的 .md 已经能满足大部分写作需求,但当你想在正文里嵌入真正的交互组件时,
.mdx 就派上用场了。
直接引入组件
在文件顶部像写普通 JS 一样 import,然后在正文任意位置使用:
import { Button } from '@/components/ui/button';
<Button>点我</Button>下面就是一个真实渲染、可点击的 shadcn/ui 按钮:
client:* 不是可选项
上面那行如果写成 <Button>点我</Button>,页面上照样会出现一个长得一模一样的按钮——
但它点了没反应。
因为 Astro 默认只把 React 组件渲染成静态 HTML,不发送任何 JS。要让它真的能交互, 必须显式加水合指令:
| 指令 | 何时水合 | 适用 |
|---|---|---|
client:load | 页面加载即刻 | 首屏就要能用的(主题切换、搜索框) |
client:visible | 滚动进视口才加载 | 正文里的示例组件,默认选它 |
client:idle | 浏览器空闲时 | 次要交互,不急着用的 |
文章正文里的演示组件几乎总该用 client:visible——读者可能根本滚不到那里,
没必要为一个也许不会被看到的按钮提前下载 JS。
这也是「能不加就不加」的由来:每个 client:* 都会往页面里塞一份运行时。
纯展示的组件(图标、徽章、卡片外壳)不加指令,就是零 JS 的静态 HTML。
在 Astro 里用 shadcn/ui 的三个坑
这三个都不报错,只是静默地不生效,所以特别费时间。
1. <Button asChild> 会失效
想做「长得像按钮的链接」时,React 里的常规写法是 <Button asChild><a href="…">。
在 Astro 里这样写会得到一段纯文本——Astro 传进去的 children 是已经渲染好的 HTML
slot,Radix 的 Slot 没办法穿过 Astro 边界把按钮的类名合并到 <a> 上。
正确做法是直接用 buttonVariants() 取出类名:
<a href="/blog" class={buttonVariants({ variant: 'outline' })}>看文章</a>顺带的好处是这个链接完全不需要 JS。
2. 组合型组件不能拆开写在 .astro 里
<Avatar> 和 <AvatarFallback> 这种父子配套的 Radix 组件,如果当成两个独立元素写在
.astro 文件里,它们会各自变成独立的孤岛,丢掉 React context,构建时直接报
must be used within <Avatar>。
要么包成一个 .tsx 组件整体引入,要么——如果只是装饰——干脆用普通 HTML 加 Tailwind
类写掉。
3. 传样式要用 className,不是 class
给 React 组件传样式时,Astro 不会帮你把 class 转成 className。写成 class
的话,它会被 shadcn 内部的 cn(className) 直接忽略:没有报错、没有警告,页面只是
悄悄少了那段间距。
<Separator className="my-8" /> <!-- 生效 -->
<Separator class="my-8" /> <!-- 样式静默丢失 -->原生 HTML 元素照常写 class,只有 React 组件要用 className。
什么时候别用 MDX
- 只是想要一个好看的框:用 Markdown 的引用块加 CSS 就够了,别为此拉进一个组件
- 只是想放张图或一段代码:
.md本来就支持 - 同样的效果原生 HTML 能做:
<details>、<dialog>现在都很能打,不必上 React
MDX 真正的价值场景只有一个:读者需要动手操作才能理解的东西——可交互的图表、 参数可调的演示、实时预览的配置器。其余情况用普通 Markdown,越简单越好。
评论
评论加载中……