在React项目里渲染Markdown文档,最直接的方案是用dangerouslySetInnerHTML把转换后的HTML字符串插入页面,但这种方式会带来XSS安全隐患,并且在样式控制上也很被动。react-markdown采用完全不同的思路:它不使用HTML字符串作为中间层,而是把Markdown解析成语法树节点,再逐个映射成对应的React组件。这样一来,<h1>、<p>、<code>等标签都是真实的React元素,你可以像操作普通组件一样给它们添加className、style或者替换成自定义组件。同样重要的是,react-markdown默认不会输出原始HTML,除非显式开启,这从根源上减少了脚本注入的风险。

要模拟GitHub风格的README,光靠基础Markdown还不够。GitHub对标准Markdown做了扩展,支持表格、任务列表、删除线、自动链接等特性。因此在配置react-markdown时,需要引入remark-gfm插件,它负责解析这些GFM语法。再配合react-syntax-highlighter处理代码块高亮,整体显示效果就能接近GitHub仓库页面的观感。下面从安装和基础用法开始,逐步展开每个关键环节。
安装与基础渲染
首先安装核心依赖。react-markdown本身只处理Markdown到React元素的转换,GFM扩展需要额外的remark插件,代码高亮则需要单独的语法高亮库。使用npm或yarn执行以下命令:
npm install react-markdown remark-gfm react-syntax-highlighter
最简单的用法是把Markdown字符串作为子节点传给<ReactMarkdown>组件。以下示例创建了一个Readme组件,接收content属性并直接渲染:
import ReactMarkdown from 'react-markdown'
function Readme({ content }) {
return (
<div className="markdown-body">
<ReactMarkdown>{content}</ReactMarkdown>
</div>
)
}
export default Readme
此时react-markdown能够处理标题、段落、列表、引用、链接、图片以及行内代码等基础语法,但无法解析表格和任务列表,代码块也只是普通文本,没有行号和高亮。为了让内容更接近GitHub样式,还需要做两件事:加载GFM插件和自定义代码块渲染。前者负责补齐语法支持,后者负责视觉体验。
启用GitHub风味Markdown
GitHub Flavored Markdown,简称GFM,在标准CommonMark基础上增加了几类常用语法。最典型的包括表格、任务列表、删除线和URL自动链接。react-markdown通过remarkPlugins属性接收remark插件数组,把remark-gfm传入即可启用这些扩展。
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
function Readme({ content }) {
return (
<div className="markdown-body">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{content}
</ReactMarkdown>
</div>
)
}
启用GFM后,像下面这样的Markdown源文本就能正确渲染为表格和任务列表:
| 功能 | 状态 | | --- | --- | | 表格渲染 | 已完成 | | 任务列表 | 已完成 | - [x] 支持GFM - [ ] 代码高亮
表格会被转换成<table>元素,任务列表则渲染为带type="checkbox"的<input>元素。值得注意的是,react-markdown并不会自动引入任何CSS,所以表格边框、复选框样式都需要你自己提供。通常我们会把GitHub官方开源的github-markdown-css样式表引入到外壳div上,也就是前面示例中的markdown-body类名。这样整个容器内的排版、字体、表格边框、代码块底色都会自动匹配GitHub的视觉风格。
还需要注意,react-markdown默认忽略原始HTML内容。如果README中直接写了<img>或<div>,默认情况下这些标签不会渲染,而是被当作纯文本显示。如果确有展示原始HTML的需求,可以设置skipHtml={false},但这样做会降低安全性,需要配合HTML净化工具使用。对大多数README展示场景,建议保持默认关闭状态。
代码高亮与组件覆盖
GitHub风格的README中,代码块通常带有深色或浅色背景、语法着色和行号。react-markdown把代码块默认渲染为<pre>结构,并不会自动加高亮。要实现高亮,需要覆盖...</pre>code组件的渲染逻辑。通常用react-syntax-highlighter的Prism或Light实现,并根据代码块是否包含语言信息来选择不同的高亮方式。
react-markdown提供了components属性,允许你传入一个组件映射对象,键是HTML标签名,值是自定义组件。下面是一个典型实现,将code组件替换为高亮组件:
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
function CodeBlock({ inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '')
if (!inline && match) {
return (
<SyntaxHighlighter
style={oneDark}
language={match[1]}
PreTag="div"
{...props}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
)
}
return (
<code className={className} {...props}>
{children}
</code>
)
}
function Readme({ content }) {
return (
<div className="markdown-body">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{ code: CodeBlock }}
>
{content}
</ReactMarkdown>
</div>
)
}
在上面的代码中,inline属性用来区分行内代码和块级代码。行内代码保持简单样式,块级代码则提取language-xxx类名,交给SyntaxHighlighter处理。oneDark是深色主题,你可以换成oneLight、github等主题,或者直接使用react-syntax-highlighter内置的GitHub风格主题。如果代码块没有指定语言,则不会进入高亮分支,而是保持普通<code>输出。
除了代码块,你还可以覆盖其他标签。例如给所有标题加锚点、为链接设置target="_blank"、把表格包一层滚动容器以适配移动端。组件映射非常灵活,但要注意标签的属性传递。react-markdown会把href传给你自定义的链接组件,你可以在这个组件内部再决定是否添加安全校验,比如限制javascript:协议。
安全处理与性能优化
react-markdown本身不会渲染原始HTML,因此基础安全性优于字符串注入方案。但Markdown中的链接和图片仍然可能带有javascript:、data:等危险协议,如果不对这些URL做过滤,用户点击后仍可能触发脚本执行。react-markdown提供了urlTransform属性,可以统一处理所有URL,返回安全的地址或空字符串来阻止跳转。
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
function safeUrl(url) {
if (/^(https?:|mailto:|#)/i.test(url)) {
return url
}
return ''
}
function Readme({ content }) {
return (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
urlTransform={safeUrl}
>
{content}
</ReactMarkdown>
)
}
如果需要允许用户输入有限的原始HTML,比如在README中嵌入视频或自定义标签,可以搭配rehype-sanitize插件。该插件基于hast-util-sanitize的默认白名单,只保留安全的标签和属性。使用方式如下:
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import rehypeSanitize from 'rehype-sanitize'
function Readme({ content }) {
return (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeSanitize]}
skipHtml={false}
>
{content}
</ReactMarkdown>
)
}
这里必须显式设置skipHtml={false},否则原始HTML仍然会被忽略。同时rehype-sanitize会过滤掉脚本、事件处理属性和危险协议,从而在保留部分HTML能力的同时维持安全边界。
性能方面,react-markdown会缓存解析结果,但对非常长的文档,渲染大量React组件仍可能消耗较多内存。如果你只展示静态README,可以考虑使用React.memo包裹Readme组件,避免父组件重复渲染导致Markdown重新解析。另外,语法高亮库的体积较大,建议使用PrismLight按需注册语言,而不是全量引入Prism。对于移动端,可以去掉行号并延迟渲染非首屏代码块,但这些优化需要根据实际场景权衡。
最后梳理一下完整方案:安装react-markdown、remark-gfm、react-syntax-highlighter三个核心依赖,给外壳div挂上markdown-body样式类,通过components覆盖代码块组件实现高亮,用urlTransform或rehype-sanitize加固安全。这样就能在React应用中展示接近GitHub仓库风格的README内容,同时保持组件化渲染的灵活性和安全性。
react-markdownGitHub风格Markdown渲染修改时间:2026-08-25 08:52:12