# React 组件与 API markstream-react 提供与 markstream-vue 相同强大的组件,但专为 React 构建。所有组件都支持 React 18+ 并包含完整的 TypeScript 支持。 根入口、`markstream-react/next`、`markstream-react/server` 现在都会产出独立声明文件。像 `NodeRendererProps`、`NodeRendererCodeBlockProps`、`NodeComponentProps`、`RenderContext`、`RenderNodeFn`、`CustomComponentMap`、`CodeBlockMonacoOptions`、`MarkdownCodeBlockNodeProps`、`ListItemNodeProps`、`HtmlPreviewFrameProps`、`TooltipProps`、`TooltipPlacement`、`LinkNodeStyleProps` 这类共享渲染器与组件类型,都可以直接从你实际使用的入口导入。 ## 主组件:MarkdownRender 在 React 中渲染 markdown 内容的主要组件。 ### Props `MarkdownRender` 使用 `markstream-react` 的 `NodeRendererProps`。 #### 核心 props | 属性 | 类型 | 默认值 | 描述 | |------|------|---------|-------------| | `content` | `string` | - | 要渲染的 Markdown 内容 | | `nodes` | `BaseNode[]` | - | 预解析的 AST 节点(通常为 `ParsedNode[]`) | | `customId` | `string` | - | 作用域标识,用于 `setCustomComponents` 与样式隔离 | | `final` | `boolean` | `false` | 标记输入结束,停止输出 streaming `loading` 节点 | | `parseOptions` | `ParseOptions` | - | 解析选项与 token hooks(仅在传入 `content` 时生效) | | `customHtmlTags` | `readonly string[]` | - | 作为自定义节点输出的 HTML-like 标签(如 `thinking`) | | `customMarkdownIt` | `(md: MarkdownIt) => MarkdownIt` | - | 自定义 MarkdownIt 实例 | | `debugPerformance` | `boolean` | `false` | 输出解析/渲染耗时与虚拟化统计(仅 dev) | | `isDark` | `boolean` | `false` | 暗色主题标记,转发给重型节点并在根容器加 `.dark` | | `indexKey` | `number \| string` | - | 列表渲染时的 key 前缀 | | `typewriter` | `boolean` | `true` | 非代码节点进入动画 | | `showTooltips` | `boolean` | `true` | 全局控制 `LinkNode` 与代码块节点 tooltip | #### 流式与重节点开关 | 属性 | 默认值 | 描述 | |------|---------|-------------| | `renderCodeBlocksAsPre` | `false` | 将 `code_block` 渲染为 `
`(Mermaid/D2/Infographic 也会随之回退) |
| `codeBlockStream` | `true` | 随内容到达流式更新代码块 |
| `viewportPriority` | `true` | 将 Monaco/Mermaid/D2/KaTeX 等重型工作延迟到接近视口时 |
| `deferNodesUntilVisible` | `true` | 重型节点先占位,接近可视区再渲染(仅非虚拟化模式) |

#### 性能(虚拟化与批次渲染)

| 属性 | 默认值 | 描述 |
|------|---------|-------------|
| `maxLiveNodes` | `320` | DOM 最大保留节点数(设为 `0` 关闭虚拟化) |
| `liveNodeBuffer` | `60` | 视窗前后 overscan 缓冲 |
| `batchRendering` | `true` | 在关闭虚拟化时启用批次渲染 |
| `initialRenderBatchSize` | `40` | 批次渲染前先渲染的节点数量 |
| `renderBatchSize` | `80` | 每个批次渲染的节点数量 |
| `renderBatchDelay` | `16` | 每次批次前的额外延迟(ms) |
| `renderBatchBudgetMs` | `6` | 自适应批次缩小前的预算(ms) |
| `renderBatchIdleTimeoutMs` | `120` | `requestIdleCallback` 超时(ms) |

#### 代码块全局配置

| 属性 | 类型 | 描述 |
|------|------|-------------|
| `codeBlockDarkTheme` | `CodeBlockMonacoTheme` | 转发到每个 `CodeBlockNode` 的 Monaco 深色主题 |
| `codeBlockLightTheme` | `CodeBlockMonacoTheme` | 转发到每个 `CodeBlockNode` 的 Monaco 浅色主题 |
| `codeBlockMonacoOptions` | `CodeBlockMonacoOptions` | 转发到 `stream-monaco` 的选项,包括 `diffHunkActionsOnHover`、`diffHunkHoverHideDelayMs`、`onDiffHunkAction` 这类 diff 悬浮操作配置 |
| `codeBlockMinWidth` | `string \| number` | 转发到 `CodeBlockNode` 的最小宽度 |
| `codeBlockMaxWidth` | `string \| number` | 转发到 `CodeBlockNode` 的最大宽度 |
| `codeBlockProps` | `NodeRendererCodeBlockProps` | 额外转发到每个代码块渲染器(`CodeBlockNode` / `MarkdownCodeBlockNode`)的 props |
| `mermaidProps` | `Partial>` | 额外转发到 Mermaid 围栏和自定义 `mermaid` 渲染器的 props |
| `d2Props` | `Partial>` | 额外转发到 D2 围栏和自定义 `d2` 渲染器的 props |
| `infographicProps` | `Partial>` | 额外转发到 infographic 围栏和自定义 `infographic` 渲染器的 props |
| `themes` | `CodeBlockMonacoTheme[]` | 转发到 `stream-monaco` 的主题列表 |

#### 重型渲染器 props 透传

`NodeRenderer` 可以直接给重型图表节点传调优参数:

```tsx

```

流式建议:
- 保持 `viewportPriority` 开启,避免离屏 Mermaid / Monaco / D2 在文字仍在流式更新时继续做后台工作。
- 高频 SSE 更推荐直接传 `nodes`,而不是每个 chunk 都重跑整篇 `content` 解析。
- Mermaid 常用调优项包括:`renderDebounceMs`、`contentStableDelayMs`、`previewPollDelayMs`、`previewPollMaxDelayMs`、`previewPollMaxAttempts`。

`NodeRendererCodeBlockProps` 会跟随公开的 `CodeBlockNode` props 结构(去掉 `node`),所以像 `showHeader`、`showFontSizeButtons`、`showTooltips` 这类字段都能直接获得补全,而不需要退回 `any`。

```tsx
import type { NodeRendererCodeBlockProps } from 'markstream-react'

const codeBlockProps: NodeRendererCodeBlockProps = {
  showHeader: false,
  showFontSizeButtons: false,
  showTooltips: false,
}
```

#### 事件

| 属性 | 类型 | 描述 |
|------|------|-------------|
| `onCopy` | `(code: string) => void` | 代码块复制事件 |
| `onHandleArtifactClick` | `(payload: any) => void` | 预览/制品点击事件 |
| `onClick` | `(event: React.MouseEvent) => void` | 根容器点击事件 |
| `onMouseOver` | `(event: React.MouseEvent) => void` | 根容器鼠标悬停事件 |
| `onMouseOut` | `(event: React.MouseEvent) => void` | 根容器鼠标移出事件 |

### 使用

```tsx
import MarkdownRender from 'markstream-react'

function App() {
  const markdown = `# Hello React!

这是 markstream-react。`

  return (
    
  )
}
```

## 代码块组件

### MarkdownCodeBlockNode

使用 Shiki 的轻量级代码高亮。

```tsx
import { MarkdownCodeBlockNode } from 'markstream-react'

function CodeBlock() {
  const codeNode = {
    type: 'code_block',
    language: 'javascript',
    code: 'const hello = "world"',
    raw: 'const hello = "world"'
  }

  const handleCopy = () => {
    alert('代码已复制!')
  }

  return (
    
) } ``` ### CodeBlockNode 功能丰富的 Monaco 驱动代码块。 ```tsx import { CodeBlockNode } from 'markstream-react' function MonacoCodeBlock() { const codeNode = { type: 'code_block', language: 'typescript', code: 'const greeting: string = "Hello"', raw: 'const greeting: string = "Hello"' } const handleCopy = (code: string) => { console.log('代码已复制:', code) } const handlePreviewCode = (artifact: any) => { console.log('预览代码:', artifact) } return (
) } ``` ## 数学组件 ### MathBlockNode 使用 KaTeX 渲染块级数学公式。 ```tsx import { MathBlockNode } from 'markstream-react' function MathBlock() { const mathNode = { type: 'math_block', content: '\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}', raw: '\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}' } return (
) } ``` ### MathInlineNode 渲染行内数学公式。 ```tsx import { MathInlineNode } from 'markstream-react' function MathInline() { const inlineMathNode = { type: 'math_inline', content: 'E = mc^2', raw: 'E = mc^2' } return (

公式如下: {' '}

) } ``` ## Mermaid 图表 ### MermaidBlockNode 渐进式 Mermaid 图表渲染。 #### 快速要点 - **Tooltip 控制**:`showTooltips` 默认 `true`;设为 `false` 可关闭头部按钮 tooltip。 ```tsx import { MermaidBlockNode } from 'markstream-react' function MermaidDiagram() { const mermaidNode = { type: 'code_block', language: 'mermaid', code: `graph TD A[开始] --> B{能用吗?} B -->|是| C[太好了!]`, raw: '' } const handleExport = (ev: any) => { console.log('Mermaid SVG:', ev.svgString) } return (
) } ``` 事件说明: - `onCopy(code: string)` 直接收到源码字符串(React 版本没有 `MermaidBlockEvent` 包装)。 - `onExport` / `onOpenModal` / `onToggleMode` 接收 `MermaidBlockEvent`,可用 `ev.preventDefault()` 阻止默认行为。 - `onToggleMode` 签名:`(target: 'source' | 'preview', ev)`。 - 如果是在 `MarkdownRender` / `NodeRenderer` 中统一使用,优先通过 `mermaidProps` 传这些参数。 ## D2 图表 ### D2BlockNode 渐进式 D2 图表渲染,失败时保留上次成功的预览。 ```tsx import { D2BlockNode } from 'markstream-react' function D2Diagram() { const d2Node = { type: 'code_block', language: 'd2', code: `direction: right Client -> API: request API -> DB: query`, raw: '' } return (
) } ``` ## 其他节点组件 ### HeadingNode ```tsx import { HeadingNode } from 'markstream-react' function CustomHeading() { const headingNode = { type: 'heading', level: 1 } return Hello World } ``` ### ParagraphNode ```tsx import { ParagraphNode } from 'markstream-react' function CustomParagraph() { const paragraphNode = { type: 'paragraph' } return ( 这是一个 {' '} 粗体 {' '} 单词。 ) } ``` ### ListNode ```tsx import { ListNode, renderNode } from 'markstream-react' function CustomList() { const listNode = { type: 'list', ordered: false, items: [ { type: 'list_item', children: [ { type: 'paragraph', children: [{ type: 'text', content: '项目 1' }] } ] }, { type: 'list_item', children: [ { type: 'paragraph', children: [{ type: 'text', content: '项目 2' }] } ] } ] } const ctx = { events: {} } return } ``` ### LinkNode ```tsx import { LinkNode } from 'markstream-react' function CustomLink() { const linkNode = { type: 'link', href: 'https://example.com', title: '示例', text: '点击我' } return ( ) } ``` ### ImageNode ```tsx import { ImageNode } from 'markstream-react' function CustomImage() { const imageNode = { type: 'image', src: 'https://example.com/image.jpg', alt: '示例图片', title: '示例', raw: '![示例图片](https://example.com/image.jpg)' } const handleClick = () => { console.log('图片被点击!') } const handleLoad = () => { console.log('图片已加载!') } return ( ) } ``` ## 工具函数 ### getMarkdown 获取配置好的 markdown-it 实例。 ```tsx import { getMarkdown } from 'stream-markdown-parser' const md = getMarkdown('my-msg-id', { html: true, linkify: true, typographer: true }) const tokens = md.parse('# Hello World') ``` ### parseMarkdownToStructure 将 markdown 字符串解析为 AST 结构。 ```tsx import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser' const md = getMarkdown() const nodes = parseMarkdownToStructure('# 标题\n\n这里的内容...', md) // 与 MarkdownRender 一起使用 // ``` ### 可选 Worker(Mermaid / KaTeX) 安装后会自动加载 Mermaid/KaTeX。如需线程外解析/渲染,可注入 Worker: ```tsx import { setKaTeXWorker, setMermaidWorker } from 'markstream-react' import KatexWorker from 'markstream-react/workers/katexRenderer.worker?worker' import MermaidWorker from 'markstream-react/workers/mermaidParser.worker?worker' setMermaidWorker(new MermaidWorker()) setKaTeXWorker(new KatexWorker()) ``` ## 自定义组件 API ### Props 接口 所有自定义节点组件都接收这些 props: ```tsx import type { CustomComponentMap, NodeComponentProps, RenderContext, RenderNodeFn } from 'markstream-react' interface NodeComponentProps { node: TNode // 解析后的节点数据 ctx?: RenderContext // 渲染上下文(主题、事件、开关) renderNode?: RenderNodeFn // 子节点渲染助手 indexKey?: React.Key // 节点的唯一键 customId?: string // 作用域标识符 isDark?: boolean typewriter?: boolean children?: React.ReactNode } ``` `CustomComponentMap` 就是 `setCustomComponents(...)` 接收的映射类型。 ### 示例自定义组件 ```tsx import React from 'react' interface CustomParagraphProps { node: { type: string children: Array<{ type: string content?: string children?: any[] }> } indexKey?: number | string customId?: string } function CustomParagraph({ node, indexKey, customId }: CustomParagraphProps) { return (

{node.children.map((child, i) => ( {child.content || ''} ))}

) } // 使用 function App() { const paragraphNode = { type: 'paragraph', children: [ { type: 'text', content: '自定义段落内容' } ] } return } ``` ## Context + 自定义组件 可以在自定义节点组件内部使用 React Context,同时通过 `setCustomComponents` 注册组件: ```tsx import MarkdownRender, { setCustomComponents } from 'markstream-react' import React, { createContext, useContext } from 'react' const ThemeContext = createContext<'light' | 'dark'>('light') function CustomHeading({ node, customId }: any) { const theme = useContext(ThemeContext) const level = node.level || 1 const Tag = `h${level}` as keyof JSX.IntrinsicElements return ( {node.children?.map((child: any, i: number) => ( {child.content || ''} ))} ) } setCustomComponents('docs', { heading: CustomHeading }) function App() { const markdown = `# 自定义标题 这使用了自定义标题组件。 ` return ( ) } ``` ## 流式传输支持 markstream-react 支持流式 markdown 内容: ```tsx import MarkdownRender from 'markstream-react' import { useEffect, useState } from 'react' function StreamingDemo() { const [content, setContent] = useState('') const fullContent = `# 流式传输演示 此内容正在**逐步**流式传输。 ` useEffect(() => { let i = 0 const interval = setInterval(() => { if (i < fullContent.length) { setContent(prev => prev + fullContent[i]) i++ } else { clearInterval(interval) } }, 30) return () => clearInterval(interval) }, []) return } ``` ## TypeScript 支持 markstream-react 包含完整的 TypeScript 类型定义: ```tsx import type { NodeComponentProps, NodeRendererProps } from 'markstream-react' import type { ParsedNode } from 'stream-markdown-parser' import MarkdownRender from 'markstream-react' function App() { const markdown = '# Hello TypeScript!' const nodes: ParsedNode[] = [] return } ``` 代码块相关的 props 类型(`CodeBlockNodeProps` / `MermaidBlockNodeProps` / `D2BlockNodeProps` / `InfographicBlockNodeProps` / `PreCodeNodeProps`)统一使用 `stream-markdown-parser` 的 `CodeBlockNode`(用 `language: 'mermaid'` / `language: 'd2'` / `language: 'd2lang'` / `language: 'infographic'` 区分渲染器)。 ## Next.js 最佳实践 优先使用专门的 Next SSR 双入口,而不是 `mounted` guard 或 `ssr: false`。 ```tsx import MarkdownRender from 'markstream-react/next' export default function MarkdownPage() { return } ``` 如果你需要纯服务端渲染路径: ```tsx import MarkdownRender from 'markstream-react/server' export default function MarkdownPage() { return } ``` 完整入口模型见 [React Next SSR](/zh/guide/react-next-ssr)。 ## Hooks 集成 你可以轻松地与 React hooks 集成: ```tsx import type { ChangeEvent } from 'react' import MarkdownRender from 'markstream-react' import { useCallback, useMemo, useState } from 'react' function MarkdownEditor() { const [content, setContent] = useState('# 编辑我!') const [theme, setTheme] = useState('light') const memoizedContent = useMemo(() => content, [content]) const handleChange = useCallback((e: ChangeEvent) => { setContent(e.target.value) }, []) return (