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 转换工具

基础使用:

javascript 复制代码
import React from 'react'
import {createRoot} from 'react-dom/client'
import Markdown from 'react-markdown'

const markdown = '# Hi, *Pluto*!'

createRoot(document.body).render(<Markdown>{markdown}</Markdown>)

二、插件rehype-raw

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

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

javascript 复制代码
<ReactMarkdown rehypePlugins={[rehypeRaw]}>
  {content}
</ReactMarkdown>

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

示例:

javascript 复制代码
const aa = {
  content:
    '<span class="ref" data-id="doc1">[1]</span>识别到当前用户诉求...'
};

import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';

<ReactMarkdown rehypePlugins={[rehypeRaw]}>
  {aa.content}
</ReactMarkdown>

这样 <span class="ref"> 就能被正常渲染

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

  • [1] 悬浮显示 segmentContent

  • 点击跳转参考文档

  • 自动编号

  • 多文档引用自动合并

javascript 复制代码
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import { Tooltip } from 'antd';

<ReactMarkdown
  rehypePlugins={[rehypeRaw]}
  components={{
    span({node, ...props}) {
      if (props.className === 'ref') {
        const id = props['data-id'];
        return (
          <Tooltip title={`文档来源:${id}`}>
            <span style={{ color: 'blue', cursor: 'pointer' }}>
              {props.children}
            </span>
          </Tooltip>
        );
      }
      return <span {...props} />;
    }
  }}
>
  {aa.content}
</ReactMarkdown>

三、rehype-sanitize

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

安全风险场景:

javascript 复制代码
// 大模型可能返回的危险内容
const dangerousContent = `
# 看起来正常的回复

<div onclick="alert('XSS')">点击我</div>
<script>stealCookies();</script>
<img src="x" onerror="maliciousCode()">
<iframe src="javascript:alert('attack')"></iframe>
<a href="javascript:stealData()">安全链接</a>
`;

// 如果没有 rehype-sanitize,这些代码会被执行!

使用方法:

javascript 复制代码
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import rehypeSanitize from 'rehype-sanitize';

const SafeMarkdownRenderer = ({ content }) => {
  return (
    <ReactMarkdown
      remarkPlugins={[remarkGfm]}
      rehypePlugins={[
        rehypeRaw,        // 允许解析 HTML
        rehypeSanitize,   // 净化 HTML,防止 XSS
      ]}
    >
      {content}
    </ReactMarkdown>
  );
};

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: 等)

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

javascript 复制代码
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 (
    <ReactMarkdown
      rehypePlugins={[
        rehypeRaw,
        [rehypeSanitize, sanitizeOptions] // 传入配置
      ]}
    >
      {content}
    </ReactMarkdown>
  );
};

四、remark-gfm

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

包括:

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

使用方法:

javascript 复制代码
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 (
    <div className="markdown-container">
      <ReactMarkdown
        remarkPlugins={[remarkGfm]}
        rehypePlugins={[rehypeRaw]}
        components={{
          // 自定义表格渲染
          table: ({ children }) => (
            <div className="table-wrapper">
              <table className="gfm-table">{children}</table>
            </div>
          ),
          // 自定义任务列表项
          input: ({ checked, node }) => {
            const type = node?.properties?.type;
            if (type === 'checkbox') {
              return (
                <input 
                  type="checkbox" 
                  checked={checked || false}
                  readOnly 
                  className="task-checkbox"
                />
              );
            }
            return <input {...node.properties} />;
          },
          // 处理删除线
          del: ({ children }) => (
            <span className="strikethrough-text">{children}</span>
          )
        }}
      >
        {markdownContent}
      </ReactMarkdown>
    </div>
  );
};

五、总结

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

javascript 复制代码
<ReactMarkdown
  remarkPlugins={[remarkGfm]}
  rehypePlugins={[rehypeRaw, rehypeSanitize]}>
  {content}
</ReactMarkdown>
  1. 完整可用的 React Markdown 渲染组件
  2. 支持流式渲染、GFM、HTML、表格、任务列表
  3. 防止 XSS 攻击

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

javascript 复制代码
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<ChatItem[]>([]);
  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 (
    <div>
      <h3>AI 对话助手</h3>

      <div
        style={{
          border: '1px solid #ccc',
          padding: 10,
          height: 400,
          overflowY: 'auto',
          display: 'flex',
          flexDirection: 'column',
          gap: 15,
        }}
      >
        {chatList.map((item) => (
          <div key={item.id} style={{ whiteSpace: 'pre-wrap' }}>
            <ReactMarkdown
              remarkPlugins={[remarkGfm]}
              rehypePlugins={[rehypeRaw]}
            >
              {item.content}
            </ReactMarkdown>
          </div>
        ))}
      </div>

      {generating && <div>生成中...</div>}
    </div>
  );
}

安装依赖:

bash 复制代码
npm install react-markdown remark-gfm rehype-raw
相关推荐
空中海32 分钟前
01 React Native 基础、核心组件与布局体系
javascript·react native·react.js
空中海1 小时前
05 React架构设计、项目实践与专家清单
前端·react.js·前端框架
我是发哥哈1 小时前
深度评测:五款主流AI培训平台的课程交付能力对比
大数据·人工智能·学习·机器学习·ai·chatgpt
空中海3 小时前
04 工程化、质量体系与 React 生态
前端·ubuntu·react.js
空中海3 小时前
03 性能、动画与 React Native 新架构
react native·react.js·架构
空中海5 小时前
02 React Native状态、导航、数据流与设备能力
javascript·react native·react.js
空中海6 小时前
04 React Native工程化、质量、发布与生态选型
javascript·react native·react.js
huisheng_qaq6 小时前
【AI入门篇-02】深入理解ChatGPT发展流程
人工智能·gpt·ai·chatgpt·大模型·transfomer
郑生zs8 小时前
Hooks-useEffect
react.js
光影少年8 小时前
react函数组件、类组件、纯组件、受控/非受控组件
前端·react.js·掘金·金石计划