本文适配前端开发场景提供 React/Vue/Svelte/Angular 全框架实现方案附带代码高亮与最佳实践。文章目录1. Markdown 渲染原理2. 环境搭建useStream 配置2.1 类型定义TypeScript2.2 基础流式组件React 示例3. 框架适配Markdown 库选择安装对应依赖4. 自定义 Markdown 组件实现4.1 React 组件4.2 Vue 组件Vue 3 TypeScript4.3 Svelte 组件4.4 Angular 组件5. HTML 输出安全XSS 防护核心原则净化示例通用6. 流式渲染性能优化6.1 节流渲染避免频繁更新6.2 增量解析超大文本优化7. Markdown 样式定制8. 生产环境最佳实践1. Markdown 渲染原理LLM 天然支持输出 Markdown 格式内容标题、列表、代码块、表格等直接渲染为纯文本会浪费结构化信息。LangChain 前端流式渲染流程分为 3 步接收流数据useStream钩子实时累积流式文本更新msg.text状态解析 Markdown将原始文本转换为 HTML 或 React 元素树单次解析 5msDOM 渲染React 用虚拟 DOM _diffingVue/Svelte/Angular 用安全 HTML 指令渲染2. 环境搭建useStream 配置首先安装依赖以 npm 为例# 核心依赖npminstalllangchain/core langchain/react# 类型支持TypeScript 项目npminstall-Dtypes/langchain__core2.1 类型定义TypeScriptimporttype{BaseMessage}fromlangchain/core/messages;// 匹配 Agent 状态结构的接口interfaceAgentState{messages:BaseMessage[];// 消息列表}2.2 基础流式组件React 示例import { useStream } from langchain/react; import { AIMessage, HumanMessage } from langchain/core/messages; import { Markdown } from ./Markdown; // 后续实现的自定义组件 // Agent 服务地址替换为你的实际地址 const AGENT_URL http://localhost:2024; export function Chat() { // 初始化流式连接 const stream useStreamAgentState({ apiUrl: AGENT_URL, assistantId: simple_agent, // 替换为你的 Agent ID }); return ( div classNamechat-container {/* 渲染消息列表 */} {stream.messages.map((msg) ( div key{msg.id} classNamemessage-bubble {AIMessage.isInstance(msg) ? ( // AI 消息用自定义 Markdown 组件渲染 Markdown{msg.text}/Markdown ) : HumanMessage.isInstance(msg) ? ( // 人类消息普通文本渲染 p{msg.text}/p ) : null} /div ))} /div ); }3. 框架适配Markdown 库选择不同前端框架推荐对应的 Markdown 解析库兼顾性能与安全性框架推荐库组合输出格式核心优势Reactreact-markdownremark-gfmReact 元素组件化渲染支持虚拟 DOM diffing无需dangerouslySetInnerHTMLVuemarkeddompurify安全 HTML轻量快速原生支持 GitHub Flavored MarkdownGFMSveltemarkeddompurify安全 HTML与 Vue 一致的 API适配 Svelte 模板语法{html}Angularmarkeddompurify安全 HTML兼容 Angular[innerHTML]指令防护 XSS 攻击安装对应依赖# Reactnpminstallreact-markdown remark-gfm# Vue/Svelte/Angularnpminstallmarked dompurify4. 自定义 Markdown 组件实现4.1 React 组件import ReactMarkdown from react-markdown; import remarkGfm from remark-gfm; interface MarkdownProps { children: string; // 接收 Markdown 文本 } export function Markdown({ children }: MarkdownProps) { // 过滤空内容避免渲染空容器 if (!children.trim()) return null; return ( div classNamemarkdown-content ReactMarkdown remarkPlugins{[remarkGfm]} // 启用 GFM 特性表格、任务列表等 options{{ breaks: true, // 单换行转为 br }} {children} /ReactMarkdown /div ); }4.2 Vue 组件Vue 3 TypeScripttemplate div classmarkdown-content v-htmlsafeHtml/div /template script setup langts import { computed } from vue; import marked from marked; import DOMPurify from dompurify; const props defineProps{ content: string; }(); // 解析 sanitize 处理 const safeHtml computed(() { if (!props.content.trim()) return ; // 启用 GFM 和换行转换 const rawHtml marked(props.content, { gfm: true, breaks: true }); // sanitize HTML 防止 XSS return DOMPurify.sanitize(rawHtml); }); /script4.3 Svelte 组件script langts import marked from marked; import DOMPurify from dompurify; export let content: string; $: rawHtml marked(content, { gfm: true, breaks: true }); $: safeHtml DOMPurify.sanitize(rawHtml); /script {#if content.trim()} div classmarkdown-content {html} safeHtml / {/if}4.4 Angular 组件// markdown.component.tsimport{Component,Input}fromangular/core;importmarkedfrommarked;importDOMPurifyfromdompurify;Component({selector:app-markdown,template:div classmarkdown-content [innerHTML]safeHtml/div,})exportclassMarkdownComponent{Input()content;getsafeHtml():string{if(!this.content.trim())return;constrawHtmlmarked(this.content,{gfm:true,breaks:true});returnDOMPurify.sanitize(rawHtml);}}5. HTML 输出安全XSS 防护LLM 输出可能包含恶意 HTML 片段如script标签、onclick事件必须进行安全净化核心原则React 例外react-markdown直接生成 React 元素无需额外 sanitize其他框架使用dompurify过滤危险内容官方仓库净化示例通用importDOMPurifyfromdompurify;// 原始 HTML可能含恶意代码constrawHtmlmarked(llmOutput);// 安全净化后输出constsafeHtmlDOMPurify.sanitize(rawHtml,{// 可选自定义允许的标签/属性ADD_TAGS:[iframe],ADD_ATTR:[allowfullscreen],});Dompurify 会自动过滤script、iframe默认等危险标签onclick、onload等事件属性javascript:伪协议链接其他 XSS 攻击向量6. 流式渲染性能优化useStream会在每个 Token 到达时更新状态触发 Markdown 重解析。默认方案适用于 99% 场景单条消息 50KB以下是极端场景优化6.1 节流渲染避免频繁更新// React 示例使用 requestAnimationFrame 节流 import { useRef, useState, useEffect } from react; export function ThrottledMarkdown({ children }: { children: string }) { const [throttledText, setThrottledText] useState(children); const textRef useRef(children); const frameRef useRefnumber | null(null); useEffect(() { textRef.current children; if (!frameRef.current) { frameRef.current requestAnimationFrame(() { setThrottledText(textRef.current); frameRef.current null; }); } return () { if (frameRef.current) cancelAnimationFrame(frameRef.current); }; }, [children]); return ( div classNamemarkdown-content ReactMarkdown remarkPlugins{[remarkGfm]} {throttledText} /ReactMarkdown /div ); }6.2 增量解析超大文本优化// 核心思路只解析新增内容避免全量重解析constparsedBufferuseRefstring[]([]);// 存储已解析的 HTML 片段constlastTextRefuseRef();// 存储上一次的完整文本// 对比新旧文本只解析新增部分constparseIncremental(newText:string){constdiffnewText.slice(lastTextRef.current.length);if(diff){constparsedDiffmarked(diff);parsedBuffer.current.push(parsedDiff);}lastTextRef.currentnewText;returnDOMPurify.sanitize(parsedBuffer.current.join());};7. Markdown 样式定制为聊天场景优化的紧凑样式适配气泡布局直接复制到全局 CSS/* Markdown 渲染容器基础样式 */.markdown-content{font-size:0.9rem;line-height:1.5;color:#333;}/* 段落间距 */.markdown-content p{margin:0.4em 0;}/* 列表样式 */.markdown-content ul, .markdown-content ol{margin:0.4em 0;padding-left:1.4em;}/* 代码块样式 */.markdown-content pre{overflow-x:auto;border-radius:0.375rem;background:rgba(0,0,0,0.05);padding:0.8rem;margin:0.8em 0;font-size:0.85rem;}/* 行内代码样式 */.markdown-content code{border-radius:0.25rem;background:rgba(0,0,0,0.08);padding:0.125rem 0.25rem;font-size:0.85rem;}/* 引用样式 */.markdown-content blockquote{margin:0.8em 0;padding-left:0.75em;border-left:3px solid #ccc;opacity:0.8;}/* 表格样式 */.markdown-content table{border-collapse:collapse;margin:0.8em 0;width:100%;}.markdown-content th, .markdown-content td{border:1px solid #e5e7eb;padding:0.4em 0.6em;text-align:left;}.markdown-content th{background:rgba(0,0,0,0.03);}/* 链接样式 */.markdown-content a{color:#2563eb;text-decoration:underline;}.markdown-content a:hover{color:#1d4ed8;}8. 生产环境最佳实践强制启用 GFMLLM 普遍使用 GitHub 风格 Markdown必须开启gfm: true严格 sanitizeVue/Svelte/Angular 务必通过dompurify处理 HTML处理空内容避免渲染空的 Markdown 容器提升 UI 整洁度启用换行转换设置breaks: true让 LLM 的单换行正常显示适配聊天布局使用紧凑样式避免大间距影响气泡布局测试边界场景验证以下内容渲染效果超长代码块横向滚动宽表格响应式适配嵌套列表缩进正确特殊字符如 $、、*监控性能通过 Chrome DevTools Performance 面板排查长消息50KB的渲染卡顿
18、LangChain 前端:模式 => Markdown 消息
发布时间:2026/6/7 20:13:34
本文适配前端开发场景提供 React/Vue/Svelte/Angular 全框架实现方案附带代码高亮与最佳实践。文章目录1. Markdown 渲染原理2. 环境搭建useStream 配置2.1 类型定义TypeScript2.2 基础流式组件React 示例3. 框架适配Markdown 库选择安装对应依赖4. 自定义 Markdown 组件实现4.1 React 组件4.2 Vue 组件Vue 3 TypeScript4.3 Svelte 组件4.4 Angular 组件5. HTML 输出安全XSS 防护核心原则净化示例通用6. 流式渲染性能优化6.1 节流渲染避免频繁更新6.2 增量解析超大文本优化7. Markdown 样式定制8. 生产环境最佳实践1. Markdown 渲染原理LLM 天然支持输出 Markdown 格式内容标题、列表、代码块、表格等直接渲染为纯文本会浪费结构化信息。LangChain 前端流式渲染流程分为 3 步接收流数据useStream钩子实时累积流式文本更新msg.text状态解析 Markdown将原始文本转换为 HTML 或 React 元素树单次解析 5msDOM 渲染React 用虚拟 DOM _diffingVue/Svelte/Angular 用安全 HTML 指令渲染2. 环境搭建useStream 配置首先安装依赖以 npm 为例# 核心依赖npminstalllangchain/core langchain/react# 类型支持TypeScript 项目npminstall-Dtypes/langchain__core2.1 类型定义TypeScriptimporttype{BaseMessage}fromlangchain/core/messages;// 匹配 Agent 状态结构的接口interfaceAgentState{messages:BaseMessage[];// 消息列表}2.2 基础流式组件React 示例import { useStream } from langchain/react; import { AIMessage, HumanMessage } from langchain/core/messages; import { Markdown } from ./Markdown; // 后续实现的自定义组件 // Agent 服务地址替换为你的实际地址 const AGENT_URL http://localhost:2024; export function Chat() { // 初始化流式连接 const stream useStreamAgentState({ apiUrl: AGENT_URL, assistantId: simple_agent, // 替换为你的 Agent ID }); return ( div classNamechat-container {/* 渲染消息列表 */} {stream.messages.map((msg) ( div key{msg.id} classNamemessage-bubble {AIMessage.isInstance(msg) ? ( // AI 消息用自定义 Markdown 组件渲染 Markdown{msg.text}/Markdown ) : HumanMessage.isInstance(msg) ? ( // 人类消息普通文本渲染 p{msg.text}/p ) : null} /div ))} /div ); }3. 框架适配Markdown 库选择不同前端框架推荐对应的 Markdown 解析库兼顾性能与安全性框架推荐库组合输出格式核心优势Reactreact-markdownremark-gfmReact 元素组件化渲染支持虚拟 DOM diffing无需dangerouslySetInnerHTMLVuemarkeddompurify安全 HTML轻量快速原生支持 GitHub Flavored MarkdownGFMSveltemarkeddompurify安全 HTML与 Vue 一致的 API适配 Svelte 模板语法{html}Angularmarkeddompurify安全 HTML兼容 Angular[innerHTML]指令防护 XSS 攻击安装对应依赖# Reactnpminstallreact-markdown remark-gfm# Vue/Svelte/Angularnpminstallmarked dompurify4. 自定义 Markdown 组件实现4.1 React 组件import ReactMarkdown from react-markdown; import remarkGfm from remark-gfm; interface MarkdownProps { children: string; // 接收 Markdown 文本 } export function Markdown({ children }: MarkdownProps) { // 过滤空内容避免渲染空容器 if (!children.trim()) return null; return ( div classNamemarkdown-content ReactMarkdown remarkPlugins{[remarkGfm]} // 启用 GFM 特性表格、任务列表等 options{{ breaks: true, // 单换行转为 br }} {children} /ReactMarkdown /div ); }4.2 Vue 组件Vue 3 TypeScripttemplate div classmarkdown-content v-htmlsafeHtml/div /template script setup langts import { computed } from vue; import marked from marked; import DOMPurify from dompurify; const props defineProps{ content: string; }(); // 解析 sanitize 处理 const safeHtml computed(() { if (!props.content.trim()) return ; // 启用 GFM 和换行转换 const rawHtml marked(props.content, { gfm: true, breaks: true }); // sanitize HTML 防止 XSS return DOMPurify.sanitize(rawHtml); }); /script4.3 Svelte 组件script langts import marked from marked; import DOMPurify from dompurify; export let content: string; $: rawHtml marked(content, { gfm: true, breaks: true }); $: safeHtml DOMPurify.sanitize(rawHtml); /script {#if content.trim()} div classmarkdown-content {html} safeHtml / {/if}4.4 Angular 组件// markdown.component.tsimport{Component,Input}fromangular/core;importmarkedfrommarked;importDOMPurifyfromdompurify;Component({selector:app-markdown,template:div classmarkdown-content [innerHTML]safeHtml/div,})exportclassMarkdownComponent{Input()content;getsafeHtml():string{if(!this.content.trim())return;constrawHtmlmarked(this.content,{gfm:true,breaks:true});returnDOMPurify.sanitize(rawHtml);}}5. HTML 输出安全XSS 防护LLM 输出可能包含恶意 HTML 片段如script标签、onclick事件必须进行安全净化核心原则React 例外react-markdown直接生成 React 元素无需额外 sanitize其他框架使用dompurify过滤危险内容官方仓库净化示例通用importDOMPurifyfromdompurify;// 原始 HTML可能含恶意代码constrawHtmlmarked(llmOutput);// 安全净化后输出constsafeHtmlDOMPurify.sanitize(rawHtml,{// 可选自定义允许的标签/属性ADD_TAGS:[iframe],ADD_ATTR:[allowfullscreen],});Dompurify 会自动过滤script、iframe默认等危险标签onclick、onload等事件属性javascript:伪协议链接其他 XSS 攻击向量6. 流式渲染性能优化useStream会在每个 Token 到达时更新状态触发 Markdown 重解析。默认方案适用于 99% 场景单条消息 50KB以下是极端场景优化6.1 节流渲染避免频繁更新// React 示例使用 requestAnimationFrame 节流 import { useRef, useState, useEffect } from react; export function ThrottledMarkdown({ children }: { children: string }) { const [throttledText, setThrottledText] useState(children); const textRef useRef(children); const frameRef useRefnumber | null(null); useEffect(() { textRef.current children; if (!frameRef.current) { frameRef.current requestAnimationFrame(() { setThrottledText(textRef.current); frameRef.current null; }); } return () { if (frameRef.current) cancelAnimationFrame(frameRef.current); }; }, [children]); return ( div classNamemarkdown-content ReactMarkdown remarkPlugins{[remarkGfm]} {throttledText} /ReactMarkdown /div ); }6.2 增量解析超大文本优化// 核心思路只解析新增内容避免全量重解析constparsedBufferuseRefstring[]([]);// 存储已解析的 HTML 片段constlastTextRefuseRef();// 存储上一次的完整文本// 对比新旧文本只解析新增部分constparseIncremental(newText:string){constdiffnewText.slice(lastTextRef.current.length);if(diff){constparsedDiffmarked(diff);parsedBuffer.current.push(parsedDiff);}lastTextRef.currentnewText;returnDOMPurify.sanitize(parsedBuffer.current.join());};7. Markdown 样式定制为聊天场景优化的紧凑样式适配气泡布局直接复制到全局 CSS/* Markdown 渲染容器基础样式 */.markdown-content{font-size:0.9rem;line-height:1.5;color:#333;}/* 段落间距 */.markdown-content p{margin:0.4em 0;}/* 列表样式 */.markdown-content ul, .markdown-content ol{margin:0.4em 0;padding-left:1.4em;}/* 代码块样式 */.markdown-content pre{overflow-x:auto;border-radius:0.375rem;background:rgba(0,0,0,0.05);padding:0.8rem;margin:0.8em 0;font-size:0.85rem;}/* 行内代码样式 */.markdown-content code{border-radius:0.25rem;background:rgba(0,0,0,0.08);padding:0.125rem 0.25rem;font-size:0.85rem;}/* 引用样式 */.markdown-content blockquote{margin:0.8em 0;padding-left:0.75em;border-left:3px solid #ccc;opacity:0.8;}/* 表格样式 */.markdown-content table{border-collapse:collapse;margin:0.8em 0;width:100%;}.markdown-content th, .markdown-content td{border:1px solid #e5e7eb;padding:0.4em 0.6em;text-align:left;}.markdown-content th{background:rgba(0,0,0,0.03);}/* 链接样式 */.markdown-content a{color:#2563eb;text-decoration:underline;}.markdown-content a:hover{color:#1d4ed8;}8. 生产环境最佳实践强制启用 GFMLLM 普遍使用 GitHub 风格 Markdown必须开启gfm: true严格 sanitizeVue/Svelte/Angular 务必通过dompurify处理 HTML处理空内容避免渲染空的 Markdown 容器提升 UI 整洁度启用换行转换设置breaks: true让 LLM 的单换行正常显示适配聊天布局使用紧凑样式避免大间距影响气泡布局测试边界场景验证以下内容渲染效果超长代码块横向滚动宽表格响应式适配嵌套列表缩进正确特殊字符如 $、、*监控性能通过 Chrome DevTools Performance 面板排查长消息50KB的渲染卡顿