Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

ds-markdown

onshinpei2kMIT0.1.8TypeScript support: included

🚀 高性能 React Markdown 打字动画组件,完美复刻 DeepSeek 聊天界面效果

markdown, deepseek, deepseek-markdown, deepseek-markdown-react, react, react-component, react-markdown

readme

ds-markdown

🚀 高性能 React Markdown 打字动画组件,完美复刻 DeepSeek 聊天界面效果

🇨🇳 中文 | 🇺🇸 English

一个专为现代 AI 应用设计的 React 组件,提供流畅的实时打字动画和完整的 Markdown 渲染能力。

npm version npm downloads bundle size React TypeScript

📖 在线演示

DEMO:🔧 StackBlitz 体验

如果你想要一个纯净的react markdown 打字组件,可以使用react-markdown-typer


❓ 为什么要用 ds-markdown?

  • AI 聊天体验极致还原
    1:1 复刻 DeepSeek 等主流 AI 聊天界面的打字动画和流式响应,带来真实的"AI 正在思考/回答"体验,极大提升用户沉浸感。

  • 后端流式数据完美适配
    很多 AI/LLM 后端接口(如 OpenAI、DeepSeek 等)推送的数据 chunk 往往一次包含多个字符,普通打字机实现会出现卡顿、跳字等问题。
    ds-markdown 会自动将每个 chunk 拆分为单个字符,逐字流畅渲染动画,无论后端一次推送多少字,都能保证每个字都流畅打字。

  • 完整 Markdown & 数学公式支持
    内置 KaTeX,支持所有主流 Markdown 语法和数学公式,适合技术问答、教育、知识库等内容丰富的应用。

  • 极致开发体验
    丰富的命令式 API,支持流式数据、异步回调、插件扩展,开发者可灵活控制动画和内容。

  • 轻量高性能
    体积小、性能优,适配移动端和桌面端。核心依赖 react-markdown(业界主流、成熟的 Markdown 渲染库),无其它重量级依赖,开箱即用。

  • 多主题与插件化架构
    支持亮/暗主题切换,兼容 remark/rehype 插件,满足个性化和高级扩展需求。

  • 适用场景广泛

    • AI 聊天机器人/助手
    • 实时问答/知识库
    • 教育/数学/编程内容展示
    • 产品演示、交互式文档
    • 任何需要"打字机"动画和流式 Markdown 渲染的场景

📋 目录


✨ 核心特性

🤖 AI 对话场景

  • 1:1 复刻 DeepSeek 官网 聊天响应效果
  • 支持思考过程 (thinking) 和回答内容 (answer) 双模式
  • 流式数据完美适配,零延迟响应用户输入

📊 内容展示场景

  • 完整 Markdown 语法支持,包括代码高亮、表格、列表等
  • 数学公式渲染 (KaTeX),支持 $...$\[...\] 语法
  • 支持亮色/暗色主题,适配不同产品风格
  • 插件化架构,支持 remark/rehype 插件扩展

🔧 开发体验

  • 支持打字中断 stop 和继续 resume
  • 支持打字关闭与开启

🎬 流畅动画

  • 双模式定时器优化,支持requestAnimationFramesetTimeout模式
  • 高频打字支持(requestAnimationFrame模式下打字间隔最低可接近于0ms
  • 帧同步渲染,与浏览器刷新完美配合
  • 智能字符批量处理,视觉效果更自然

📦 快速安装

# npm
npm install ds-markdown

# yarn
yarn add ds-markdown

# pnpm
pnpm add ds-markdown

通过 ESM CDN 使用

无需安装,直接在浏览器中使用:

DEMO

<!-- 导入样式, 必须 -->
<link rel="stylesheet" href="https://esm.sh/ds-markdown/dist/style.css" />

<!-- 导入katex数学公式样式, 非不要不引入 -->
<link rel="stylesheet" href="https://esm.sh/ds-markdown/dist/katex.css" />

<!-- 导入组件 -->
<script type="module">
  import Markdown from 'https://esm.sh/ds-markdown';
</script>

🚀 5分钟上手

基础用法

DEMO

import DsMarkdown from 'ds-markdown';
import 'ds-markdown/style.css';

function App() {
  return (
    <DsMarkdown interval={20} answerType="answer">
      # Hello ds-markdown 这是一个**高性能**的打字动画组件! ## 特性 - ⚡ 零延迟流式处理 - 🎬 流畅打字动画 - 🎯 完美语法支持
    </DsMarkdown>
  );
}

禁用打字动画

import DsMarkdown from 'ds-markdown';
import 'ds-markdown/style.css';

function StaticDemo() {
  const [disableTyping, setDisableTyping] = useState(false);

  return (
    <div>
      <button onClick={() => setDisableTyping(!disableTyping)}>{disableTyping ? '开启' : '关闭'}打字机效果</button>

      <DsMarkdown interval={20} answerType="answer" disableTyping={disableTyping}>
        # 静态展示模式 当 `disableTyping` 为 `true` 时,内容会立即全部显示,无打字动画效果。 这在某些场景下非常有用: - 📄 静态文档展示 - 🔄 切换显示模式 - ⚡ 快速预览内容
      </DsMarkdown>
    </div>
  );
}

数学公式支持

import DsMarkdown from 'ds-markdown';
// 如果需要展示公式,则需要引入公式转换插件
import { katexPlugin } from 'ds-markdown/plugins';
import 'ds-markdown/style.css';
// 如果需要展示公式,则需要引入数学公式样式
import 'ds-markdown/katex.css';

function MathDemo() {
  return (
    <DsMarkdown interval={20} answerType="answer" plugins={[katexPlugin]} math={{ splitSymbol: 'dollar' }}>
      # 勾股定理 在直角三角形中,斜边的平方等于两条直角边的平方和: $a^2 + b^2 = c^2$ 其中: - $a$ 和 $b$ 是直角边 - $c$ 是斜边 对于经典的"勾三股四弦五": $c = \sqrt{3 ^ (2 + 4) ^ 2} = \sqrt{25} = 5$
    </DsMarkdown>
  );
}

AI 对话场景

function ChatDemo() {
  const [thinking, setThinking] = useState('');
  const [answer, setAnswer] = useState('');

  const handleAsk = () => {
    setThinking('🤔 正在思考您的问题...');

    setTimeout(() => {
      setAnswer(`# 关于 React 19

React 19 带来了许多激动人心的新特性:

## 🚀 主要更新
1. **React Compiler** - 自动优化性能
2. **Actions** - 简化表单处理
3. **Document Metadata** - 内置 SEO 支持

让我们一起探索这些新功能!`);
    }, 2000);
  };

  return (
    <div>
      <button onClick={handleAsk}>询问 AI</button>

      {thinking && (
        <DsMarkdown answerType="thinking" interval={30}>
          {thinking}
        </DsMarkdown>
      )}

      {answer && (
        <DsMarkdown answerType="answer" interval={15}>
          {answer}
        </DsMarkdown>
      )}
    </div>
  );
}

🎯 高级回调控制

import { useRef, useState } from 'react';
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';

function AdvancedCallbackDemo() {
  const markdownRef = useRef<MarkdownCMDRef>(null);
  const [typingStats, setTypingStats] = useState({ progress: 0, currentChar: '', totalChars: 0 });

  const handleBeforeTypedChar = async (data) => {
    // 在字符打字前进行异步操作
    console.log('即将打字:', data.currentChar);

    // 可以在这里进行网络请求、数据验证等异步操作
    if (data.currentChar === '!') {
      await new Promise((resolve) => setTimeout(resolve, 500)); // 模拟延迟
    }
  };

  const handleTypedChar = (data) => {
    // 更新打字统计信息
    setTypingStats({
      progress: Math.round(data.percent),
      currentChar: data.currentChar,
      totalChars: data.currentIndex + 1,
    });

    // 可以在这里添加音效、动画等效果
    if (data.currentChar === '.') {
      // 播放句号音效
      console.log('播放句号音效');
    }
  };

  const handleStart = (data) => {
    console.log('开始打字:', data.currentChar);
  };

  const handleEnd = (data) => {
    console.log('打字完成:', data.str);
  };

  const startDemo = () => {
    markdownRef.current?.clear();
    markdownRef.current?.push(
      '# 高级回调演示\n\n' +
        '这个示例展示了如何使用 `onBeforeTypedChar` 和 `onTypedChar` 回调:\n\n' +
        '- 🎯 **打字前回调**:可以在字符显示前进行异步操作\n' +
        '- 📊 **打字后回调**:可以实时更新进度和添加特效\n' +
        '- ⚡ **性能优化**:支持异步操作,不影响打字流畅度\n\n' +
        '当前进度:' +
        typingStats.progress +
        '%\n' +
        '已打字数:' +
        typingStats.totalChars +
        '\n\n' +
        '这是一个非常强大的功能!',
      'answer',
    );
  };

  return (
    <div>
      <button onClick={startDemo}>🚀 开始高级演示</button>

      <div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
        <strong>打字统计:</strong> 进度 {typingStats.progress}% | 当前字符: "{typingStats.currentChar}" | 总字符数: {typingStats.totalChars}
      </div>

      <MarkdownCMD ref={markdownRef} interval={30} onBeforeTypedChar={handleBeforeTypedChar} onTypedChar={handleTypedChar} onStart={handleStart} onEnd={handleEnd} />
    </div>
  );
}

🔄 重新开始动画演示

import { useRef, useState } from 'react';
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';

function RestartDemo() {
  const markdownRef = useRef<MarkdownCMDRef>(null);
  const [isPlaying, setIsPlaying] = useState(false);
  const [hasStarted, setHasStarted] = useState(false);

  const startContent = () => {
    markdownRef.current?.clear();
    markdownRef.current?.push(
      '# 重新开始动画演示\n\n' +
        '这个示例展示了如何使用 `restart()` 方法:\n\n' +
        '- 🔄 **重新开始**:从头开始播放当前内容\n' +
        '- ⏸️ **暂停恢复**:可以随时暂停和恢复\n' +
        '- 🎯 **精确控制**:完全控制动画播放状态\n\n' +
        '当前状态:' +
        (isPlaying ? '播放中' : '已暂停') +
        '\n\n' +
        '这是一个非常实用的功能!',
      'answer',
    );
    setIsPlaying(true);
  };

  const handleStart = () => {
    if (hasStarted) {
      // 如果已经开始过,则重新开始
      markdownRef.current?.restart();
    } else {
      // 第一次开始
      markdownRef.current?.start();
      setHasStarted(true);
    }
    setIsPlaying(true);
  };

  const handleStop = () => {
    markdownRef.current?.stop();
    setIsPlaying(false);
  };

  const handleResume = () => {
    markdownRef.current?.resume();
    setIsPlaying(true);
  };

  const handleRestart = () => {
    markdownRef.current?.restart();
    setIsPlaying(true);
  };

  const handleEnd = () => {
    setIsPlaying(false);
  };

  return (
    <div>
      <div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
        <button onClick={startContent}>🚀 开始内容</button>
        <button onClick={handleStart} disabled={isPlaying}>
          {hasStarted ? '🔄 重新开始' : '▶️ 开始'}
        </button>
        <button onClick={handleStop} disabled={!isPlaying}>
          ⏸️ 暂停
        </button>
        <button onClick={handleResume} disabled={isPlaying}>
          ▶️ 恢复
        </button>
        <button onClick={handleRestart}>🔄 重新开始</button>
      </div>

      <div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
        <strong>动画状态:</strong> {isPlaying ? '🟢 播放中' : '🔴 已暂停'}
      </div>

      <MarkdownCMD ref={markdownRef} interval={25} onEnd={handleEnd} />
    </div>
  );
}

▶️ 手动开始动画演示

import { useRef, useState } from 'react';
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';

function StartDemo() {
  const markdownRef = useRef<MarkdownCMDRef>(null);
  const [isPlaying, setIsPlaying] = useState(false);
  const [hasStarted, setHasStarted] = useState(false);

  const loadContent = () => {
    markdownRef.current?.clear();
    markdownRef.current?.push(
      '# 手动开始动画演示\n\n' +
        '这个示例展示了如何使用 `start()` 方法:\n\n' +
        '- 🎯 **手动控制**:当 `autoStartTyping=false` 时,需要手动调用 `start()`\n' +
        '- ⏱️ **延迟开始**:可以在用户交互后开始动画\n' +
        '- 🎮 **游戏化**:适合需要用户主动触发的场景\n\n' +
        '点击"开始动画"按钮来手动启动打字效果!',
      'answer',
    );
    setIsPlaying(false);
  };

  const handleStart = () => {
    if (hasStarted) {
      // 如果已经开始过,则重新开始
      markdownRef.current?.restart();
    } else {
      // 第一次开始
      markdownRef.current?.start();
      setHasStarted(true);
    }
    setIsPlaying(true);
  };

  const handleEnd = () => {
    setIsPlaying(false);
  };

  return (
    <div>
      <div style={{ marginBottom: '10px', display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
        <button onClick={loadContent}>📝 加载内容</button>
        <button onClick={handleStart} disabled={isPlaying}>
          {hasStarted ? '🔄 重新开始' : '▶️ 开始动画'}
        </button>
      </div>

      <div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
        <strong>状态:</strong> {isPlaying ? '🟢 动画播放中' : '🔴 等待开始'}
      </div>

      <MarkdownCMD ref={markdownRef} interval={30} autoStartTyping={false} onEnd={handleEnd} />
    </div>
  );
}

📚 完整 API 文档

默认导出 DsMarkdown 和 MarkdownCMD 的 props

import DsMarkdown, { MarkdownCMD } from 'ds-markdown';
属性 类型 说明 默认值
interval number 打字间隔 (毫秒) 30
timerType 'setTimeout' | 'requestAnimationFrame' 定时器类型,不支持动态修改 当前默认值是setTimeout,后期会改为requestAnimationFrame
answerType 'thinking' | 'answer' 内容类型 (影响样式主题),不支持动态修改 'answer'
theme 'light' | 'dark' 主题类型 'light'
plugins IMarkdownPlugin[] 插件配置 []
math IMarkdownMath 数学公式配置 { splitSymbol: 'dollar' }
onEnd (data: EndData) => void 打字结束回调 -
onStart (data: StartData) => void 打字开始回调 -
onBeforeTypedChar (data: IBeforeTypedChar) => Promise<void> 字符打字前的回调,支持异步操作,会阻塞之后的打字 -
onTypedChar (data: ITypedChar) => void 每字符打字后的回调 -
disableTyping boolean 禁用打字动画效果 false
autoStartTyping boolean 是否自动开始打字动画,设为 false 时需手动触发,不支持动态修改 true
codeBlock IMarkdownCode 代码块配置 {headerActions: true}

注意: 如果当在打字中 disableTypingtrue 变为 false,则在下一个打字触发时,会把剩下的所有字一次性显示

IBeforeTypedChar

属性 类型 说明 默认值
currentIndex number 当前字符在整个字符串中的索引 0
currentChar string 当前即将打字的字符 -
answerType AnswerType 内容类型 (thinking/answer) -
prevStr string 当前类型内容的前缀字符串 -
percent number 打字进度百分比 (0-100) 0

ITypedChar

属性 类型 说明 默认值
currentIndex number 当前字符在整个字符串中的索引 0
currentChar string 当前已打字的字符 -
answerType AnswerType 内容类型 (thinking/answer) -
prevStr string 当前类型内容的前缀字符串 -
currentStr string 当前类型内容的完整字符串 -
percent number 打字进度百分比 (0-100) 0

IMarkdownMath

属性 类型 说明 默认值
splitSymbol 'dollar' | 'bracket' 数学公式分隔符类型 'dollar'

分隔符说明:

  • 'dollar':使用 $...$$$...$$ 语法
  • 'bracket':使用 \(...\)\[...\] 语法

IMarkdownPlugin

属性 类型 说明 默认值
remarkPlugin unknown remark 插件 -
rehypePlugin unknown rehype 插件 -
type 'buildIn' | 'custom' 插件类型 -
id any 插件唯一标识 -

组件暴露的方法

默认导出 DsMarkdown

方法 参数 说明
start - 开始打字动画
stop - 暂停打字动画
resume - 恢复打字动画
restart - 重新开始打字动画,从头开始播放当前内容

MarkdownCMD 暴露的方法

方法 参数 说明
push (content: string, answerType: AnswerType) 添加内容并开始打字
clear - 清空所有内容和状态
triggerWholeEnd - 手动触发完成回调
start - 开始打字动画
stop - 暂停打字动画
resume - 恢复打字动画
restart - 重新开始打字动画,从头开始播放当前内容

用法示例:

markdownRef.current?.start(); // 开始动画
markdownRef.current?.stop(); // 暂停动画
markdownRef.current?.resume(); // 恢复动画
markdownRef.current?.restart(); // 重新开始动画

🧮 数学公式使用指南

DEMO1:勾股定理

DEMO2:题目解答

基本语法

import { katexPlugin } from 'ds-markdown/plugins';

// 1. 启用数学公式支持
<DsMarkdown plugins={[katexPlugin]}>
  # 数学公式示例

  // 行内公式
  这是一个行内公式:$E = mc^2$

  // 块级公式
  $$\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}$$
</DsMarkdown>

分隔符选择

// 使用美元符号分隔符(默认)
<DsMarkdown
  plugins={[katexPlugin]}
  math={{ splitSymbol: 'dollar' }}
>
  行内:$a + b = c$
  块级:$$\sum_{i=1}^{n} x_i = x_1 + x_2 + \cdots + x_n$$
</DsMarkdown>

// 使用括号分隔符
<DsMarkdown
  plugins={[katexPlugin]}
  math={{ splitSymbol: 'bracket' }}
>
  行内:\(a + b = c\)
  块级:\[\sum_{i=1}^{n} x_i = x_1 + x_2 + \cdots + x_n\]
</DsMarkdown>

流式数学公式

// 完美支持流式输出中的数学公式
const mathContent = [
  '勾股定理:',
  '$a^2 + b^2 = c^2$',
  '\n\n',
  '其中:',
  '- $a$ 和 $b$ 是直角边\n',
  '- $c$ 是斜边\n\n',
  '对于经典的"勾三股四弦五":\n',
  '$c = \\sqrt{3^2 + 4^2} = \\sqrt{25} = 5$\n\n',
  '这个定理在几何学中有着广泛的应用!',
];

mathContent.forEach((chunk) => {
  markdownRef.current?.push(chunk, 'answer');
});

样式定制

/* 数学公式样式定制 */
.katex {
  font-size: 1.1em;
}

.katex-display {
  margin: 1em 0;
  text-align: center;
}

/* 暗色主题适配 */
[data-theme='dark'] .katex {
  color: #e1e1e1;
}

🔌 插件系统

内置插件

KaTeX 数学公式插件

import { katexPlugin } from 'ds-markdown/plugins';

// 启用数学公式支持
<DsMarkdown plugins={[katexPlugin]}>数学公式:$E = mc^2$</DsMarkdown>;

自定义插件

import { createBuildInPlugin } from 'ds-markdown/plugins';

// 创建自定义插件
const customPlugin = createBuildInPlugin({
  remarkPlugin: yourRemarkPlugin,
  rehypePlugin: yourRehypePlugin,
  id: Symbol('custom-plugin'),
});

// 使用自定义插件
<DsMarkdown plugins={[katexPlugin, customPlugin]}>内容</DsMarkdown>;

🎛️ 定时器模式详解

requestAnimationFrame 模式 🌟 (推荐)

// 🎯 特性
- 时间驱动:基于真实经过时间计算字符数量
- 批量处理:单帧内可处理多个字符
- 帧同步:与浏览器 60fps 刷新率同步
- 高频优化:完美支持 interval < 16ms 的高速打字

// 🎯 适用场景
- 现代 Web 应用的默认选择
- 追求流畅动画效果
- 高频打字 (interval > 0 即可)
- AI 实时对话场景

setTimeout 模式 📟 (兼容)

// 🎯 特性
- 单字符:每次精确处理一个字符
- 固定间隔:严格按设定时间执行
- 节拍感:经典打字机的节奏感
- 精确控制:适合特定时序要求

// 🎯 适用场景
- 需要精确时间控制
- 营造复古打字机效果
- 兼容性要求较高的场景

📊 性能对比

特性 requestAnimationFrame setTimeout
字符处理 每帧可处理多个字符 每次处理一个字符
高频间隔 ✅ 优秀 (5ms → 每帧3字符) ❌ 可能卡顿
低频间隔 ✅ 正常 (100ms → 6帧后1字符) ✅ 精确
视觉效果 🎬 流畅动画感 ⚡ 精确节拍感
性能开销 🟢 低 (帧同步) 🟡 中等 (定时器)

高频推荐requestAnimationFrame,低频推荐 setTimeout

多语言配置

ConfigProvider 是 ds-markdown 提供的多语言配置组件,用于管理应用中的国际化文本。

基本用法

import React from 'react';
import { ConfigProvider } from 'ds-markdown';
import zhCN from 'ds-markdown/i18n/zh';

const App: React.FC = () => {
  return (
    <ConfigProvider locale={zhCN}>
      <YourApp />
    </ConfigProvider>
  );
};

可用的语言包

中文 (zhCN)

import zhCN from 'ds-markdown/i18n/zh';

英文 (enUS)

import enUS from 'ds-markdown/i18n/en';

在组件中使用语言包

使用 locale 属性来切换自己想要的语言包,例如切换到英文

import React from 'react';
import DsMarkdown from 'ds-markdown';
import { ConfigProvider } from 'ds-markdown';
import en from 'ds-markdown/i18n/en';

const MyComponent: React.FC = () => {
  return (
    <ConfigProvider locale={en}>
      <DsMarkdown {...props} />
    </ConfigProvider>
  );
};

语言包结构

当前支持的语言包包含以下字段:

interface Locale {
  codeBlock: {
    copy: string;
    copied: string;
    download: string;
  };
  [key: string]: string;
}

注意事项

  1. ConfigProvider 必须包裹在使用 useLocale 的组件外层
  2. 语言包对象会被缓存,避免不必要的重新渲染
  3. 支持扩展自定义的语言包字段
  4. 如果没有提供 ConfigProvider,会使用默认的中文语言包

💡 实战示例

📝 AI 流式对话

DEMO: 🔧 StackBlitz 体验

import { useRef } from 'react';
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';

function StreamingChat() {
  const markdownRef = useRef<MarkdownCMDRef>(null);

  // 模拟 AI 流式响应
  const simulateAIResponse = async () => {
    markdownRef.current?.clear();

    // 思考阶段
    markdownRef.current?.push('🤔 正在分析您的问题...', 'thinking');
    await delay(1000);
    markdownRef.current?.push('\n\n✅ 分析完成,开始回答', 'thinking');

    // 流式回答
    const chunks = [
      '# React 19 新特性解析\n\n',
      '## 🚀 React Compiler\n',
      'React 19 最大的亮点是引入了 **React Compiler**:\n\n',
      '- 🎯 **自动优化**:无需手动 memo 和 useMemo\n',
      '- ⚡ **性能提升**:编译时优化,运行时零开销\n',
      '- 🔧 **向后兼容**:现有代码无需修改\n\n',
      '## 📝 Actions 简化表单\n',
      '新的 Actions API 让表单处理变得更简单:\n\n',
      '```tsx\n',
      'function ContactForm({ action }) {\n',
      '  const [state, formAction] = useActionState(action, null);\n',
      '  return (\n',
      '    <form action={formAction}>\n',
      '      <input name="email" type="email" />\n',
      '      <button>提交</button>\n',
      '    </form>\n',
      '  );\n',
      '}\n',
      '```\n\n',
      '希望这个解答对您有帮助!🎉',
    ];

    for (const chunk of chunks) {
      await delay(100);
      markdownRef.current?.push(chunk, 'answer');
    }
  };

  return (
    <div className="chat-container">
      <button onClick={simulateAIResponse}>🤖 询问 React 19 新特性</button>

      <MarkdownCMD ref={markdownRef} interval={10} timerType="requestAnimationFrame" onEnd={(data) => console.log('段落完成:', data)} />
    </div>
  );
}

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

🧮 数学公式流式渲染

import { katexPlugin } from 'ds-markdown/plugins';

function MathStreamingDemo() {
  const markdownRef = useRef<MarkdownCMDRef>(null);

  const simulateMathResponse = async () => {
    markdownRef.current?.clear();

    const mathChunks = [
      '# 勾股定理详解\n\n',
      '在直角三角形中,斜边的平方等于两条直角边的平方和:\n\n',
      '$a^2 + b^2 = c^2$\n\n',
      '其中:\n',
      '- $a$ 和 $b$ 是直角边\n',
      '- $c$ 是斜边\n\n',
      '对于经典的"勾三股四弦五":\n',
      '$c = \\sqrt{3^2 + 4^2} = \\sqrt{25} = 5$\n\n',
      '这个定理在几何学中有着广泛的应用!',
    ];

    for (const chunk of mathChunks) {
      await delay(150);
      markdownRef.current?.push(chunk, 'answer');
    }
  };

  return (
    <div>
      <button onClick={simulateMathResponse}>📐 讲解勾股定理</button>

      <MarkdownCMD ref={markdownRef} interval={20} timerType="requestAnimationFrame" plugins={[katexPlugin]} math={{ splitSymbol: 'dollar' }} />
    </div>
  );
}

🎯 高级回调控制

import { useRef, useState } from 'react';
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';

function AdvancedCallbackDemo() {
  const markdownRef = useRef<MarkdownCMDRef>(null);
  const [typingStats, setTypingStats] = useState({ progress: 0, currentChar: '', totalChars: 0 });

  const handleBeforeTypedChar = async (data) => {
    // 在字符打字前进行异步操作
    console.log('即将打字:', data.currentChar);

    // 可以在这里进行网络请求、数据验证等异步操作
    if (data.currentChar === '!') {
      await new Promise((resolve) => setTimeout(resolve, 500)); // 模拟延迟
    }
  };

  const handleTypedChar = (data) => {
    // 更新打字统计信息
    setTypingStats({
      progress: Math.round(data.percent),
      currentChar: data.currentChar,
      totalChars: data.currentIndex + 1,
    });

    // 可以在这里添加音效、动画等效果
    if (data.currentChar === '.') {
      // 播放句号音效
      console.log('播放句号音效');
    }
  };

  const handleStart = (data) => {
    console.log('开始打字:', data.currentChar);
  };

  const handleEnd = (data) => {
    console.log('打字完成:', data.str);
  };

  const startDemo = () => {
    markdownRef.current?.clear();
    markdownRef.current?.push(
      '# 高级回调演示\n\n' +
        '这个示例展示了如何使用 `onBeforeTypedChar` 和 `onTypedChar` 回调:\n\n' +
        '- 🎯 **打字前回调**:可以在字符显示前进行异步操作\n' +
        '- 📊 **打字后回调**:可以实时更新进度和添加特效\n' +
        '- ⚡ **性能优化**:支持异步操作,不影响打字流畅度\n\n' +
        '当前进度:' +
        typingStats.progress +
        '%\n' +
        '已打字数:' +
        typingStats.totalChars +
        '\n\n' +
        '这是一个非常强大的功能!',
      'answer',
    );
  };

  return (
    <div>
      <button onClick={startDemo}>🚀 开始高级演示</button>

      <div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5', borderRadius: '4px' }}>
        <strong>打字统计:</strong> 进度 {typingStats.progress}% | 当前字符: "{typingStats.currentChar}" | 总字符数: {typingStats.totalChars}
      </div>

      <MarkdownCMD ref={markdownRef} interval={30} onBeforeTypedChar={handleBeforeTypedChar} onTypedChar={handleTypedChar} onStart={handleStart} onEnd={handleEnd} />
    </div>
  );
}

🔧 最佳实践

1. 性能优化

// ✅ 推荐配置
<DsMarkdown
  timerType="requestAnimationFrame"
  interval={15} // 15-30ms 为最佳体验
/>

// ❌ 避免过小间隔
<DsMarkdown interval={1} /> // 可能导致性能问题

2. 流式数据处理

// ✅ 推荐:命令式 API
const ref = useRef<MarkdownCMDRef>(null);
useEffect(() => {
  ref.current?.push(newChunk, 'answer');
}, [newChunk]);

// ❌ 避免:频繁更新 children
const [content, setContent] = useState('');
// 每次更新都会重新解析整个内容

3. 数学公式优化

// ✅ 推荐:按需加载数学公式样式
import 'ds-markdown/style.css';
import 'ds-markdown/katex.css'; // 仅在需要时引入

// ✅ 推荐:合理使用分隔符
// 对于简单公式,使用 $...$ 更简洁
// 对于复杂公式,使用 $$...$$ 更清晰

// ✅ 推荐:插件化配置
import { katexPlugin } from 'ds-markdown/plugins';
<DsMarkdown plugins={[katexPlugin]}>数学公式内容</DsMarkdown>;

4. 类型安全

import { MarkdownCMDRef } from 'ds-markdown';

const ref = useRef<MarkdownCMDRef>(null);
// 完整的 TypeScript 类型提示

changelog

更新日志

本项目的所有重要变更都将记录在此文件中。

格式基于 Keep a Changelog, 本项目遵循 语义化版本

[0.1.0]

🚀 新增功能

  • 数学公式支持:添加了完整的 KaTeX 集成,提供高性能的数学公式渲染
    • 通过 splitSymbol 配置支持 $...$\[...\] 两种分隔符
    • 完美兼容打字动画的流式渲染
    • 自动适配亮色/暗色主题
    • 新增 math 属性,包含 splitSymbol 选项
    • 内置语法缓冲,处理流式输出中的不完整数学公式

🔧 架构重构

  • 插件化架构:重构数学公式支持为插件系统
    • 新增 plugins 属性,支持插件配置
    • 新增 katexPlugin 内置插件
    • 新增 createBuildInPlugin 工具函数
    • 移除 math.isOpen 属性,改为通过插件控制
    • 新增 IMarkdownPlugin 接口定义

📦 依赖更新

  • 新增 katex (^0.16.22) 用于数学公式渲染
  • 新增 rehype-katex (^7.0.1) 用于 HTML 处理
  • 新增 remark-math (^6.0.0) 用于 Markdown 处理

🔧 构建更新

  • 更新构建流程,包含 KaTeX CSS 编译
  • 新增 src/katex.less 用于 KaTeX 样式
  • 更新 package.json 构建脚本,生成 dist/katex.css

📚 文档更新

  • 更新所有语言版本的 README(中文、English、日本語、한국어)
  • 添加完整的数学公式使用指南
  • 添加流式数学公式渲染示例
  • 添加数学公式优化最佳实践
  • 添加数学公式 CSS 自定义示例
  • 添加插件系统使用指南

🎯 API 变更

  • 新增属性:为声明式和命令式 API 添加 plugins 属性
    interface IMarkdownPlugin {
      remarkPlugin?: unknown;
      rehypePlugin?: unknown;
      type: 'buildIn' | 'custom';
      id?: any;
    }
  • 修改属性math 属性移除 isOpen 选项
    interface IMarkdownMath {
      splitSymbol: 'dollar' | 'bracket'; // 分隔符类型
    }

🧪 示例更新

  • 新增 KaTeX 数学公式流式渲染示例
  • 新增完整的数学公式使用示例
  • 新增括号语法转换工具 (remarkMathBracket.ts)
  • 更新示例代码使用新的插件系统

🌐 国际化

  • 更新 README.md(中文)添加数学公式支持
  • 更新 README.en.md 添加数学公式支持
  • 更新 README.ja.md 添加数学公式支持(数式サポート)
  • 更新 README.ko.md 添加数学公式支持(수식 지원)

🔄 破坏性变更

  • 移除 math.isOpen 属性:现在需要通过 plugins 属性来启用数学公式支持
  • API 变更:使用 plugins={[katexPlugin]} 替代 math={{ isOpen: true }}

🐛 问题修复

📝 内部更新

  • 新增 src/utils/remarkMathBracket.ts 用于括号语法转换
  • 新增 src/plugins/index.tsx 插件系统实现
  • 新增 src/constant.ts 添加插件标识符
  • 增强 src/components/HighReactMarkdown/index.tsx 的插件支持
  • 更新 src/defined.ts 添加插件类型定义
  • 更新 src/MarkdownCMD/index.tsx 传递插件配置

[历史版本]

[0.0.x] - 历史版本

  • 初始版本,支持基础 Markdown 打字动画
  • 流式语法缓冲
  • 双定时器模式(requestAnimationFrame + setTimeout)
  • 主题支持(亮色/暗色)
  • TypeScript 支持
  • React 16.8+ 兼容性