基于react-markdown实现对大模型输出展示——2:自定义标签及事件触发

背景

上一篇文章 我们基于 react-markdown 并引用 remark-gfm rehype-raw github-markdown-css来处理复杂md格式,处理html标签 和美化样式,本篇章我们来实现自定义标签和事件触发

场景

举例:例如后端跟我们约定,他们流式返回数据,我们接到后拼装展示,但是有一些特殊标识,比如说引用,类似于参考文献,一般是右上角上标,点击会跳转详情或者其他操作

这里我们就需要,自定义标签,并需要触发事件,暂定 我们约定 后端返回的数据特殊格式如下, 第一个是引用的顺序,第二个是引用的id

js 复制代码
[[1,'引用1的id']]

我们需要判定此格式,转为 sup标签,并自定义样式,点击的时候,需要将id传过去,触发事件

实现方案

1:原始效果及展示

以上规则,demo代码及展示如下

js 复制代码
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import "github-markdown-css"

const richMarkdownContent = `
# 一级标题:Markdown 丰富示例 [[1,'我是1的id']]

## 二级标题:Markdown 丰富示例 [[1,'我是1的id']]

### 三级标题:Markdown 丰富示例 [[1,'我是1的id']]
`;

const App = () => {
  return (
    <div className="markdown-body">
      <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
        {richMarkdownContent}
      </ReactMarkdown>
    </div>
  );
};

export default App;

效果:

如果我们不处理,则按照字符串展示

现在我们按需截取特殊约定字符

2: 正则截取

正则截取,我们将顺序回显,id作为自定义属性添加到元素上,借助rehype-raw渲染出来自定义的标签,注意自定义属性data-supid

js 复制代码
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import "github-markdown-css"

const richMarkdownContent = `
# 一级标题:Markdown 丰富示例 [[1,'我是1的id']]

## 二级标题:Markdown 丰富示例 [[1,'我是1的id']]

### 三级标题:Markdown 丰富示例 [[1,'我是1的id']]
`;

const replaceReferences = (str)=> {
  // 定义正则表达式
  const regex = /\[\[(\d+),'(.*?)'\]\]/g;

  // 使用 replace 方法进行全局替换
  return str.replace(regex, (match, num, id) => {
      return `<sup className="text-active cursor-pointer" data-supid="${id}">[${num}]</sup>`;
  });
}

const regStr = replaceReferences(richMarkdownContent)

const App = () => {
  return (
    <div className="markdown-body">
      <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
        {regStr}
      </ReactMarkdown>
    </div>
  );
};

export default App;

此时效果如下,

3:美化样式

对于创建的元素,可以使用tailwindcss 修改样式,或者其他自定义样式均可,和普通html样式样式无异

js 复制代码
const replaceReferences = (str)=> {
  // 定义正则表达式
  const regex = /\[\[(\d+),'(.*?)'\]\]/g;

  // 使用 replace 方法进行全局替换
  return str.replace(regex, (match, num, id) => {
      return `<sup className="text-blue-600 cursor-pointer" data-supid="${id}">[${num}]</sup>`;
  });
}

注意text-blue-600颜色设置,现在效果展示如下

4:自定义事件触发

借助react-markdowncomponents来自定义标签

js 复制代码
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import "github-markdown-css"

const richMarkdownContent = `
# 一级标题:Markdown 丰富示例 [[1,'我是1的id']]

## 二级标题:Markdown 丰富示例 [[2,'我是2的id']]

### 三级标题:Markdown 丰富示例 [[3,'我是3的id']]
`;

const replaceReferences = (str) => {
  // 定义正则表达式
  const regex = /\[\[(\d+),'(.*?)'\]\]/g;

  // 使用 replace 方法进行全局替换
  return str.replace(regex, (match, num, id) => {
    return `<sup className="text-blue-600 cursor-pointer" data-supid="${id}">[${num}]</sup>`;
  });
}

const regStr = replaceReferences(richMarkdownContent)

// 自定义渲染器
const components = {
  sup: ({ children, ...rest }) => {
    return (
      <sup className="text-active" onClick={(event) => handleSupClick(event)} {...rest}>
        {children}
      </sup>
    );
  },
};

// 点击事件处理函数
const handleSupClick = (event) => {
  const supid = event.target.dataset.supid;
  console.log("Clicked sup data-supid:", supid);
  // 你可以在这里进行其他操作,比如将内容传递给父组件等
};

const App = () => {
  return (
    <div className="markdown-body">
      <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} components={components}>
        {regStr}
      </ReactMarkdown>
    </div>
  );
};

export default App;
  1. components 对象中的 sup 组件

    • onClick 事件处理函数中,将事件对象 event 传递给 handleSupClick 函数。
    • 使用扩展运算符 ...rest 传递其他属性,确保 data-supid 属性被正确传递。
  2. handleSupClick 函数

    • 通过 event.target.dataset.supid 获取自定义属性 data-supid 的值。

    • 打印获取到的值,你可以在这里进行其他操作,比如将内容传递给父组件等。

这样,当你点击 sup 元素时,就可以获取到自定义属性 data-supid 的值了。

此时的展示及展示效果如下

总结

这篇文章 我们基于自定义的sup标签,借助自定义components,结合正则来实现了文章前期的需求,可以发现功能很强大,对于多数其他标签有更特殊的需求,可以自己来实现,一家之言,有需要的按需取舍,你也可以下载项目项目地址 dev2分支

未完待续。。。

相关推荐
冰暮流星1 分钟前
javascript之String方法介绍
开发语言·javascript·ecmascript
徐小夕21 分钟前
花了3天,我写了一款开源AI公众号编辑器
前端·vue.js·github
PBitW30 分钟前
git 中容易遗忘的点 (二) ⚡⚡⚡
前端·git·面试
Hilaku38 分钟前
2026 年前端大洗牌:React 服务端到 Vite 的底层重构
前端·javascript·程序员
PBitW1 小时前
git 中容易遗忘的点 (三) 🚀🚀🚀
前端·git·面试
名字还没想好☜1 小时前
React setState 连续调用「丢更新」排查:批量更新与函数式更新
前端·javascript·react.js·react·usestate
sugar__salt1 小时前
DeepSeek-R1 WebGPU (1):在浏览器里跑大模型
react.js·reactjs·react·端模型
come112341 小时前
Vue 2 与 Vue 3 语法区别及使用技巧
前端·javascript·vue.js
腻害兔1 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:ERP 企业资源模块,一个轻量级进销存的完整实现?
前端·javascript·vue.js·人工智能·前端框架·产品经理·ai编程
虹科网络安全2 小时前
艾体宝新闻|从 SQL 注入到服务器接管:CVE-2026-57517 暴露 Web 管理面板的供应链与安全编码风险
服务器·前端·sql