Markdown 协议与解析渲染(前端 AI 应用)
适合人群:有前端基础,但没有系统学过 Markdown 协议和渲染链路的同学。
目录
- Markdown 在 AI 产品中到底是什么角色
- Markdown 解析渲染的完整链路
- 为什么不能直接 innerHTML
- 最小实现:解析 + 安全渲染
- 在流式输出场景下的处理策略
- 常见问题与排查清单
Markdown 在 AI 产品中到底是什么角色
很多 AI 应用默认输出 Markdown,不是因为“看起来专业”,而是因为它在“可读性”和“结构化”之间做了平衡。
纯文本虽然简单,但无法表达标题、列表、代码块、表格。HTML 虽然强大,但模型直接输出 HTML 风险更高,且难以约束。
Markdown 的优势在于:
- 对模型友好:模型更容易稳定输出
- 对前端友好:可以解析为 AST 再安全渲染
- 对用户友好:内容结构清晰、可复制、可二次编辑
你可以把 Markdown 理解成“受约束的富文本协议”。
Markdown 解析渲染的完整链路
从工程角度,完整链路不是“字符串 -> 页面”这么简单,而是:
每一步都不可省:
- Parser:把文本按语法规则切成结构
- AST:中间表示,方便插件处理
- Transform:比如代码高亮、数学公式、表格增强
- Sanitize:过滤恶意标签和危险属性
- Render:最终变成 React 节点
为什么不能直接 innerHTML
初学者常见做法是把模型输出当 HTML 注入页面,这非常危险。
如果输出中包含恶意脚本片段,可能直接造成 XSS 漏洞,轻则弹窗,重则盗取用户信息。
所以建议始终遵循:
- 先解析 Markdown
- 再做白名单级别清洗
- 最后渲染组件
最小实现:解析 + 安全渲染
下面给一个最小可运行思路(以 marked + DOMPurify 为例):
import { marked } from "marked";
import DOMPurify from "dompurify";
export function renderSafeMarkdown(md: string) {
const rawHtml = marked.parse(md, { breaks: true }) as string;
const safeHtml = DOMPurify.sanitize(rawHtml, {
USE_PROFILES: { html: true },
});
return safeHtml;
}
React 中使用:
export function MarkdownView({ content }: { content: string }) {
const html = renderSafeMarkdown(content);
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
这段代码完成的事情是:
- 把 Markdown 转成 HTML
- 清理潜在危险标签
- 用 React 安全展示
在流式输出场景下的处理策略
AI 聊天里最常见的问题是“流式过程中 Markdown 不完整”,例如代码块只输出了开头的 ```。
如果你每个 token 都强行渲染 Markdown,页面会抖动、格式错乱,甚至卡顿。
推荐策略:
- 流式阶段:先按纯文本渲染,保证稳定
- 输出结束:再一次性解析完整 Markdown
- 或者:每 80-120ms 批量刷新一次,降低重排频率
示例节流:
let buffer = "";
let timer: number | null = null;
export function pushToken(token: string, flush: (text: string) => void) {
buffer += token;
if (timer) return;
timer = window.setTimeout(() => {
flush(buffer);
buffer = "";
timer = null;
}, 100);
}
常见问题与排查清单
- 代码块渲染异常:检查三引号是否闭合
- 表格不显示:确认解析器是否启用 GFM
- XSS 风险:确认是否执行了 sanitize
- 页面卡顿:减少频繁重渲染,做节流与批量更新