react-markdown如何实现下划线语法 规避dangerouslySetInnerHTML风险
react-markdown 自定义下划线语法实现方案
不需要使用dangerouslySetInnerHTML,也不需要复用现有Markdown语法,基于react-markdown底层的remark插件扩展能力即可实现独立的下划线语法支持,完全保留原有Markdown渲染的安全性,同时实现**渲染粗体、__渲染下划线的需求。
具体实现步骤
安装工具依赖
插件开发只需要一个AST遍历工具包,JS/TS项目都可使用:npm install unist-util-visitTS项目额外安装类型包即可:
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
相关产品推荐
相关产品推荐

