You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

react-markdown如何实现下划线语法 规避dangerouslySetInnerHTML风险

react-markdown 自定义下划线语法实现方案

不需要使用dangerouslySetInnerHTML,也不需要复用现有Markdown语法,基于react-markdown底层的remark插件扩展能力即可实现独立的下划线语法支持,完全保留原有Markdown渲染的安全性,同时实现**渲染粗体、__渲染下划线的需求。


具体实现步骤

  • 安装工具依赖
    插件开发只需要一个AST遍历工具包,JS/TS项目都可使用:

    npm install unist-util-visit
    

    TS项目额外安装类型包即可:

    npm install -D @types/unist-util-visit
    
  • 编写自定义remark语法插件
    插件会在Markdown解析为AST的阶段,匹配__包裹的文本内容,将其识别为独立的underline类型节点,通过正则边界处理避免和默认的**粗体语法冲突:

    import { visit } from 'unist-util-visit';
    
    const remarkUnderline = () => {
      return (tree) => {
        visit(tree, 'text', (node, pos, parent) => {
          const textValue = node.value;
          // 正则匹配__包裹的内容,排除前后带*的粗体场景
          const matchReg = /(?<!\*)__([^_*]+?)__(?!\*)/g;
          const splitNodes = [];
          let lastPos = 0;
          let matchResult;
    
          while ((matchResult = matchReg.exec(textValue)) !== null) {
            // 存入匹配段之前的普通文本
            if (matchResult.index > lastPos) {
              splitNodes.push({
                type: 'text',
                value: textValue.slice(lastPos, matchResult.index)
              });
            }
            // 存入自定义下划线节点
            splitNodes.push({
              type: 'underline',
              children: [{ type: 'text', value: matchResult[1] }]
            });
            lastPos = matchResult.index + matchResult[0].length;
          }
    
          // 存入最后一段剩余普通文本
          if (lastPos < textValue.length) {
            splitNodes.push({
              type: 'text',
              value: textValue.slice(lastPos)
            });
          }
    
          // 替换原文本节点
          if (splitNodes.length) {
            parent.children.splice(pos, 1, ...splitNodes);
            return [visit.SKIP, pos + splitNodes.length];
          }
        });
      };
    };
    
  • 接入react-markdown组件
    将自定义插件传入remarkPlugins配置项,同时在components中定义underline节点的渲染规则,所有渲染走React原生DOM流程,无XSS风险:

    import ReactMarkdown from 'react-markdown';
    import remarkGfm from 'remark-gfm'; // 原有其他插件可正常保留
    
    export default function NoteMarkdown({ content }) {
      return (
        <ReactMarkdown
          remarkPlugins={[remarkGfm, remarkUnderline]}
          components={{
            // 下划线节点用<u>标签渲染,可自行加样式类
            underline: ({ children }) => <u className="text-underline">{children}</u>,
            // 原有粗体渲染逻辑不受影响
            strong: ({ children }) => <strong className="font-bold">{children}</strong>
          }}
        >
          {content}
        </ReactMarkdown>
      );
    }
    

方案说明

  • 语法识别互不干扰:测试以下Markdown内容可正常渲染,不会出现格式错乱
    **普通粗体文本**
    __普通下划线文本__
    **粗体内容内嵌__下划线内容__可正常解析**
    代码块、行内代码中的__内容不会被误识别为下划线
    
  • 安全性和原生react-markdown一致:全程没有HTML注入逻辑,所有内容由React转义后渲染,不存在dangerouslySetInnerHTML带来的XSS风险
  • 语法可灵活调整:如果需要更换下划线触发符号(比如++下划线++、~下划线~),只需要修改插件内的正则匹配规则即可,不需要改动其他渲染逻辑
  • 兼容原有扩展:和已有的remark-gfm、代码高亮、自定义链接渲染等插件无冲突

内容的提问来源于stack exchange,提问作者Guillaume Getkoz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 09:18:18