React大模型网站-流式推送markdown转换问题以及开启 rehype-raw,rehype-sanitize,remark-gfm等插件的使用 - 详解

        在React大模型网站中实现流式推送Markdown转换开启rehype-raw等支持HTML注入是一个常见且重要的需求。

  1. 实时渲染:大模型响应是流式的,需要边接收边渲染

  2. 完整支持:既要渲染标准Markdown,又要支持HTML内容

  3. 安全性:HTML注入需要可控,防止XSS攻击

一、ReactMarkdown 是什么

ReactMarkdown 是一个把 Markdown 转成 React 组件的库。

但它和 markedmarkdown-it 最大的区别是:

  1. 默认 不渲染 HTML
  2. 默认 不能把 HTML 字符串插入进去
  3. 默认 不能输出 dangerouslySetInnerHTML

它是 严格安全的 Markdown → React 转换工具

基础使用:

import React from 'react'
import {createRoot} from 'react-dom/client'
import Markdown from 'react-markdown'
const markdown = '# Hi, *Pluto*!'
createRoot(document.body).render({markdown})

二、插件rehype-raw

    rehype-raw处理 Markdown 中的原生 HTML 的插件库,专门用于 ReactMarkdown 生态。rehype-raw 是一个 rehype 插件(rehype 是 HTML 的处理器),作用是让 ReactMarkdown 能够安全地解析并渲染 Markdown 内容中的 HTML 标签

        默认情况下:<ReactMarkdown>{content}</ReactMarkdown> 不会解析 HTML 标签(例如 <span><div>sup> 等都会被直接当作文本输出)。


  {content}

加上 rehype-raw,HTML 标签就会被当作 HTML 并正常渲染

示例:

const aa = {
  content:
    '[1]识别到当前用户诉求…'
};
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';

  {aa.content}

这样  就能被正常渲染

还可以对其进行定制化操作例如:

  • [1] 悬浮显示 segmentContent

  • 点击跳转参考文档

  • 自动编号

  • 多文档引用自动合并

import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import { Tooltip } from 'antd';

            
              {props.children}
            
          
        );
      }
      return ;
    }
  }}
>
  {aa.content}

三、rehype-sanitize

        rehype-sanitize 是一个用于 sanitize(净化)HTML 内容的 rehype 插件,专门用于防止 XSS 攻击。在 React 大模型网站中,当开启 rehype-raw 支持 HTML 注入时,必须配合 rehype-sanitize 使用。

安全风险场景:

// 大模型可能返回的危险内容
const dangerousContent = `
# 看起来正常的回复
点击我
<script>stealCookies();</script> 安全链接 `; // 如果没有 rehype-sanitize,这些代码会被执行!

使用方法:

import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import rehypeSanitize from 'rehype-sanitize';
const SafeMarkdownRenderer = ({ content }) => {
  return (
    
      {content}
    
  );
};

2. 默认的安全规则

rehype-sanitize 默认使用 GitHub 的 sanitization 规则:

  • ✅ 允许:大多数安全标签(div, span, p, br, strong, em 等)

  • ✅ 允许:安全属性(class, id, style, href, src 等)

  • ❌ 禁止:<script><iframe><object><embed>

  • ❌ 禁止:事件处理器(onclick, onerror, onload 等)

  • ❌ 禁止:JavaScript URL(javascript:, data: 等)

自定义允许的标签和属性:

import ReactMarkdown from 'react-markdown';
import rehypeSanitize from 'rehype-sanitize';
const CustomSanitizeRenderer = () => {
  // 自定义 sanitize 配置
  const sanitizeOptions = {
    tagNames: [
      // 基础标签
      'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
      'p', 'br', 'hr',
      'div', 'span',
      // 列表
      'ul', 'ol', 'li',
      // 强调
      'strong', 'em', 'b', 'i', 'u',
      'del', 'ins', 's', 'strike',
      // 链接和图片
      'a', 'img',
      // 代码
      'code', 'pre', 'blockquote',
      // 表格(如果需要)
      'table', 'thead', 'tbody', 'tr', 'th', 'td',
      // 自定义标签(大模型可能使用的)
      'details', 'summary', 'kbd', 'sup', 'sub',
    ],
    attributes: {
      // 全局允许的属性
      '*': ['className', 'style', 'title', 'id'],
      // 链接允许的属性
      'a': ['href', 'target', 'rel', 'download'],
      // 图片允许的属性
      'img': ['src', 'alt', 'width', 'height', 'loading'],
      // 代码块允许的属性
      'code': ['className'], // 用于语法高亮
      // 自定义属性
      'span': ['data-*'], // 允许所有 data-* 属性
      'div': ['data-*'],
    },
    protocols: {
      // 允许的协议
      href: ['http', 'https', 'mailto', 'tel'],
      src: ['http', 'https', 'data'], // 谨慎使用 data:
    },
    strip: ['script', 'style'], // 完全移除这些标签及其内容
  };
  return (
    
      {content}
    
  );
};

四、remark-gfm

remark-gfm让 ReactMarkdown 支持 GitHub 风格 Markdown (GFM) 的官方插件。让 ReactMarkdown 支持 “更多 Markdown 语法。

包括:

功能是否需要 remark-gfm
- 无序列表不需要
1. 有序列表不需要
粗体 / 斜体不需要
表格(| col1 | col2 |)✔️ 需要
任务列表(- [x] 已完成✔️ 需要
自动链接(直接粘贴 URL 自动变成 <a>✔️ 需要
删除线(~~删除~~✔️ 需要

使用方法:

import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
const CompleteMarkdownRenderer = () => {
  const markdownContent = `
# GFM 完整示例
## 1. 表格示例
| 特性 | 说明 | 状态 |
|------|------|------|
| 表格 | 支持完整的表格语法 | ✅ |
| 删除线 | ~~过时内容标记~~ | ✅ |
| 任务列表 | 项目管理功能 | ✅ |
## 2. 自动链接
- 网站: https://github.com
- 邮箱: contact@example.com
- 普通文本不会自动链接
## 3. 任务列表
### 项目进度
- [x] 需求分析
- [x] 系统设计
- [ ] 编码实现
- [ ] 测试验证
- [ ] 部署上线
## 4. 删除线应用
原价:~~¥199~~ 现价:¥99
## 5. 表格中的复杂内容
| 项目 | 描述 | 包含 |
|------|------|------|
| 基础 | 核心功能 | **加粗**、*斜体*、\`代码\` |
| 扩展 | 高级功能 | ~~删除线~~、[链接](url) |
`;
  return (
    
(
{children}
), // 自定义任务列表项 input: ({ checked, node }) => { const type = node?.properties?.type; if (type === 'checkbox') { return ( ); } return ; }, // 处理删除线 del: ({ children }) => ( {children} ) }} > {markdownContent}
); };

五、总结

基础不需要定制化的话直接引入即可,支持大部分基础场景:


  {content}
  1. 完整可用的 React Markdown 渲染组件
  2. 支持流式渲染、GFM、HTML、表格、任务列表
  3. 防止 XSS 攻击

完整的 React SSE 推流 + Markdown 累积渲染模板:

import React, { useEffect, useState } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import _ from 'lodash';
import dayjs from 'dayjs';
interface ChatItem {
  id: string;
  role: 'user' | 'assistant';
  content: string;
  createdAt: number;
  updatedAt: number;
}
interface ChatProps {
  apiUrl: string;
  userInput: string;
}
export default function ChatAI({ apiUrl, userInput }: ChatProps) {
  const [chatList, setChatList] = useState([]);
  const [generating, setGenerating] = useState(false);
  const startGenerating = (input: string) => {
    if (!input) return;
    setGenerating(true);
    const data = { question: input };
    const queryString = `data=${encodeURIComponent(JSON.stringify(data))}`;
    const eventSource = new EventSource(`${apiUrl}?${queryString}`);
    const conversationId = Date.now().toString();
    let accumulatedContent = '';
    // 创建占位消息
    setChatList((old) => [
      ...old,
      {
        id: conversationId,
        role: 'assistant',
        content: '',
        createdAt: dayjs().valueOf(),
        updatedAt: dayjs().valueOf(),
      },
    ]);
    eventSource.onmessage = (event) => {
      const parsed = JSON.parse(event.data);
      accumulatedContent += parsed.content || '';
      // 更新 chatList,触发 React 渲染
      setChatList((old) => {
        const next = _.cloneDeep(old);
        const idx = next.findIndex((i) => i.id === conversationId);
        if (idx !== -1) {
          next[idx].content = accumulatedContent;
          next[idx].updatedAt = dayjs().valueOf();
        }
        return next;
      });
      if (parsed.type === 'End') {
        eventSource.close();
        setGenerating(false);
      }
    };
    eventSource.onerror = () => {
      eventSource.close();
      setGenerating(false);
    };
  };
  useEffect(() => {
    if (userInput) startGenerating(userInput);
  }, [userInput]);
  return (
    

AI 对话助手

{chatList.map((item) => (
{item.content}
))}
{generating &&
生成中...
}
); }

安装依赖:

npm install react-markdown remark-gfm rehype-raw

posted @ 2026-01-11 18:27  gccbuaa  阅读(2)  评论(0)    收藏  举报