Markdown component for React
维护文档说明
- 定期更新
commonmark-html链接版本
react-markdown
[![构建][徽章构建图片]][徽章构建链接] [![覆盖率][徽章覆盖率图片]][徽章覆盖率链接] [![下载次数][徽章下载次数图片]][徽章下载次数链接] [![大小][徽章大小图片]][徽章大小链接]
用于渲染 Markdown 的 React 组件。
功能亮点
目录
- 这是什么?
- 我应该什么时候使用这个?
- 安装
- 使用
- API
- 示例
- 插件
- 语法
- 兼容性
- 架构
- 附录 A:Markdown 中的 HTML
- 附录 B:组件
- 附录 C:Markdown (和 JSX) 中的行结束符
- 安全性
- 相关
- 贡献
- 许可协议
这是什么?
这个包是一个 [React][] 组件,可以接收一个 markdown 字符串,并将其安全地渲染为 React 元素。 您还可以传递插件来改变 markdown 的转换方式,以及传递将替代普通 HTML 元素的组件。
- 要学习 markdown,请查看这个 [速查表和教程][commonmark-帮助]
- 要尝试
react-markdown,请访问 我们的演示
我应该什么时候使用这个?
虽然还有其他在 React 中使用 markdown 的方法,但为什么选择这个?主要有三个原因:它们通常依赖 dangerouslySetInnerHTML,在处理 markdown 时存在 bug,或者不允许您将元素替换为组件。
react-markdown 构建了一个虚拟 DOM,因此 React 只替换发生变化的部分,基于语法树。
这之所以可能,是因为我们使用了 unified,特别是 remark 用于 markdown 和 rehype 用于 HTML,
它们是使用插件转换内容的流行工具。
这个包专注于让初学者能够安全地在 React 中使用 markdown。
当您熟悉 unified 时,您可以使用基于现代钩子的替代方案 react-remark 或 rehype-react 手动操作。
如果您想在 markdown 文件内使用 JavaScript 和 JSX,请使用 MDX。
安装
这个包仅支持 ESM。 在 Node.js(版本 16+)中,使用 [npm][npm-安装] 进行安装:
npm install react-markdown
在 Deno 环境中,使用 esm.sh:
import Markdown from 'https://esm.sh/react-markdown@10'
在支持 esm.sh 的浏览器中:
<script type="module">
import Markdown from 'https://esm.sh/react-markdown@10?bundle'
</script>
用途
一个基本的“你好,世界”示例:
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>)
显示等效的 JSX
<h1>
Hi, <em>Pluto</em>!
</h1>
以下是使用插件的示例
(如remark-gfm,它直接支持脚注、删除线、表格、任务列表和URLs):
import React from 'react'
import {createRoot} from 'react-dom/client'
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
const markdown = `Just a link: www.nasa.gov.`
createRoot(document.body).render(
<Markdown remarkPlugins={[remarkGfm]}>{markdown}</Markdown>
)
显示等效的 JSX
<p>
Just a link: <a href="http://www.nasa.gov">www.nasa.gov</a>.
</p>
API
此包导出以下标识符
MarkdownAsync,
MarkdownHooks,
以及
defaultUrlTransform。
默认导出为 Markdown。
此外,它还导出了额外的 [TypeScript][] 类型
AllowElement,
Components,
ExtraProps,
HooksOptions,
Options,
以及
UrlTransform。
Markdown
用于渲染 Markdown 的组件。
这是一个同步组件。
使用异步插件时,请参见 MarkdownAsync 或
MarkdownHooks。
参数
options(Options) —— 属性
返回
React 元素 (ReactElement)。
MarkdownAsync
用于渲染 Markdown 并支持通过 async/await 使用异步插件。
服务器端支持返回 Promise 的组件。
客户端的异步支持,请参见 MarkdownHooks。
参数
options(Options) —— 属性
返回
返回 React 元素的 Promise (Promise<ReactElement>).
MarkdownHooks
通过钩子支持异步插件来渲染 Markdown 的组件。
此组件使用 useEffect 和 useState 钩子。
钩子运行在客户端,并不会立即渲染内容。
服务器端的异步支持,请参见 MarkdownAsync。
参数
options(Options) —— 属性
返回
React 节点 (ReactNode)。
defaultUrlTransform(url)
使 URL 安全。
这种方式遵循 GitHub 的处理方式。
它允许 http、https、irc、ircs、mailto 和 xmpp 协议,
以及相对于当前协议的相对 URL(例如 /something)。
参数
url(string) —— URL
返回
安全 URL (string)。
AllowElement
过滤元素(TypeScript 类型)。
参数
node(Elementfromhast) —— 需要检查的元素index(number | undefined) ——element在parent中的索引parent(Nodefromhast) ——element的父元素
返回
是否允许 element(boolean,可选)。
Components
将标签名映射到组件(TypeScript 类型)。
类型
import type {ExtraProps} from 'react-markdown'
import type {ComponentProps, ElementType} from 'react'
type Components = {
[Key in Extract<ElementType, string>]?: ElementType<ComponentProps<Key> & ExtraProps>
}
额外属性
传递给组件的额外字段(TypeScript 类型)。
字段
node(hast中的Element,可选) —— 原始节点
钩子选项
为 MarkdownHooks 配置(TypeScript 类型);通过 fallback 属性扩展常规的 Options。
扩展
字段
fallback(ReactNode,可选) —— 在处理器处理 Markdown 时要渲染的内容
选项
配置(TypeScript 类型)。
字段
allowElement(AllowElementapi-allow-element,可选) —— 过滤元素;首先使用allowedElements/disallowedElementsallowedElements(Array<string>,默认:所有标签名) —— 允许的标签名;不能与disallowedElements结合使用children(string,可选) —— Markdowncomponents(Componentsapi-components,可选) —— 将标签名映射到组件disallowedElements(Array<string>,默认:[]) —— 禁止使用的标签名;不能与allowedElements结合使用rehypePlugins(Array<Plugin>,可选) —— 要使用的 rehype 插件列表remarkPlugins(Array<Plugin>,可选) —— 要使用的 remark 插件列表remarkRehypeOptions(remark-rehype的Options,可选) —— 传递给remark-rehype的选项skipHtml(boolean,默认:false) —— 完全忽略 Markdown 中的 HTMLunwrapDisallowed(boolean,默认:false) —— 提取(展开)禁止元素中的内容; 通常,当说不允许使用strong时,它及其子元素会被丢弃,使用unwrapDisallowed时,元素本身会被其子元素替换urlTransform(UrlTransformapi-url-transform,默认:defaultUrlTransformapi-default-url-transform) —— 改变 URLs
URL转换
转换 URLs(TypeScript 类型)。
参数
url(string) —— URLkey(string,例如:'href') —— 属性名node(hast中的Element) —— 要检查的元素
返回
转换后的 URL(string,可选)。
示例
使用插件
此示例展示如何使用 remark 插件。
在这个案例中,使用的是 remark-gfm,它直接支持删除线、表格、任务列表和 URLs:
import React from 'react'
import {createRoot} from 'react-dom/client'
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
const markdown = `A paragraph with *emphasis* and **strong importance**.
> A block quote with ~strikethrough~ and a URL: https://reactjs.org.
* Lists
* [ ] todo
* [x] done
A table:
| a | b |
| - | - |
`
createRoot(document.body).render(
<Markdown remarkPlugins={[remarkGfm]}>{markdown}</Markdown>
)
显示等效 JSX
<>
<p>
A paragraph with <em>emphasis</em> and <strong>strong importance</strong>.
</p>
<blockquote>
<p>
A block quote with <del>strikethrough</del> and a URL:{' '}
<a href="https://reactjs.org">https://reactjs.org</a>.
</p>
</blockquote>
<ul className="contains-task-list">
<li>Lists</li>
<li className="task-list-item">
<input type="checkbox" disabled /> todo
</li>
<li className="task-list-item">
<input type="checkbox" disabled checked /> done
</li>
</ul>
<p>A table:</p>
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
</table>
</>
使用带选项的插件
本示例展示了如何使用插件并为其设置选项。要实现这一点,请使用一个数组,插件放在第一位,选项放在第二位。
remark-gfm 提供了一个选项,允许仅使用双波浪线进行删除线的标记:
import React from 'react'
import {createRoot} from 'react-dom/client'
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
const markdown = 'This ~is not~ strikethrough, but ~~this is~~!'
createRoot(document.body).render(
<Markdown remarkPlugins={[[remarkGfm, {singleTilde: false}]]}>
{markdown}
</Markdown>
)
显示等价 JSX
<p>
This ~is not~ strikethrough, but <del>this is</del>!
</p>
使用自定义组件(语法高亮)
本例展示了如何通过传递一个组件来覆盖元素的默认处理方式。
在此情景中,我们使用了极其出色的 react-syntax-highlighter,由 @conorhastingsgithub-conorhastings 提供,来实现语法高亮:
import React from 'react'
import {createRoot} from 'react-dom/client'
import Markdown from 'react-markdown'
import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter'
import {dark} from 'react-syntax-highlighter/dist/esm/styles/prism'
// Did you know you can use tildes instead of backticks for code in markdown? ✨
const markdown = `Here is some JavaScript code:
~~~js
console.log('It works!')
~~~
`
createRoot(document.body).render(
<Markdown
children={markdown}
components={{
code(props) {
const {children, className, node, ...rest} = props
const match = /language-(\w+)/.exec(className || '')
return match ? (
<SyntaxHighlighter
{...rest}
PreTag="div"
children={String(children).replace(/\n$/, '')}
language={match[1]}
style={dark}
/>
) : (
<code {...rest} className={className}>
{children}
</code>
)
}
}}
/>
)
显示等价的 JSX
<>
<p>Here is some JavaScript code:</p>
<pre>
<SyntaxHighlighter language="js" style={dark} PreTag="div" children="console.log('It works!')" />
</pre>
</>
使用 remark 和 rehype 插件(数学公式)
本示例展示了如何通过语法扩展(通过 remark-math)在 Markdown 中支持数学公式,以及使用转换插件(rehype-katex)来渲染这些数学公式。
import React from 'react'
import {createRoot} from 'react-dom/client'
import Markdown from 'react-markdown'
import rehypeKatex from 'rehype-katex'
import remarkMath from 'remark-math'
import 'katex/dist/katex.min.css' // `rehype-katex` does not import the CSS for you
const markdown = `The lift coefficient ($C_L$) is a dimensionless coefficient.`
createRoot(document.body).render(
<Markdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{markdown}
</Markdown>
)
显示等效的 JSX
<p>
The lift coefficient (
<span className="katex">
<span className="katex-mathml">
<math xmlns="http://www.w3.org/1998/Math/MathML">{/* … */}</math>
</span>
<span className="katex-html" aria-hidden="true">
{/* … */}
</span>
</span>
) is a dimensionless coefficient.
</p>
插件
我们使用 unified,特别是 remark 用于 Markdown 和 rehype 用于 HTML, 这些都是通过插件转换内容的工具。以下是有三种寻找插件的好方法:
awesome-remark和awesome-rehype—— 精选最出色的项目- remark 插件列表 和 rehype 插件列表 —— 所有插件的列表
remark-plugin和rehype-plugin主题 —— GitHub 上所有标记的仓库
语法
react-markdown 默认遵循 CommonMark,以标准化不同 Markdown 实现之间的差异。
一些语法扩展通过插件支持。
我们在底层使用 micromark 进行解析。
查阅其文档以获取更多关于 Markdown、CommonMark 和扩展的信息。
兼容性
由 unified 集体维护的项目与维护版本的 Node.js 兼容。
当我们发布一个新的主要版本时,我们会放弃对不再维护的 Node 版本的支持。
这意味着我们尝试保持当前发布线,react-markdown@10,与 Node.js 16 兼容。
它们在所有现代浏览器中都能工作(基本上是除了 IE 11)。 您可以使用打包工具(如 esbuild、webpack 或 Rollup)在您的项目中使用这个包, 并使用其选项(或插件)为旧版浏览器添加支持。
架构
react-markdown
+----------------------------------------------------------------------------------------------------------------+
| |
| +----------+ +----------------+ +---------------+ +----------------+ +------------+ |
| | | | | | | | | | | |
markdown-+->+ remark +-mdast->+ remark 插件 +-mdast->+ remark-rehype +-hast->+ rehype 插件 +-hast->+ 组件 +-+->react 元素
| | | | | | | | | | | |
| +----------+ +----------------+ +---------------+ +----------------+ +------------+ |
| |
+----------------------------------------------------------------------------------------------------------------+
要了解这个项目的作用,首先重要的是了解 unified 是如何工作的:请阅读 unifiedjs/unified 的自述文件
(直到您看到 API 部分是必读的)。
react-markdown 是一个 unified 管道 —— 包装得如此,大多数用户无需直接与 unified 交互。
处理器会经历以下步骤:
- 将 Markdown 解析为 mdast(Markdown 语法树)
- 通过 remark(Markdown 生态系统)进行转换
- 将 mdast 转换为 hast(HTML 语法树)
- 通过 rehype(HTML 生态系统)进行转换
- 使用组件将 hast 渲染为 React
附录 A:Markdown 中的 HTML
react-markdown 通常会转义 HTML(或者在启用 skipHtml 时忽略它),因为它是不安全的,且违背了此库的初衷。
然而,如果您处于受信任的环境(您信任 Markdown),并且可以牺牲包大小(±60kb minzipped),那么您可以使用 rehype-raw:
import React from 'react'
import {createRoot} from 'react-dom/client'
import Markdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
const markdown = `<div class="note">
Some *emphasis* and <strong>strong</strong>!
</div>`
createRoot(document.body).render(
<Markdown rehypePlugins={[rehypeRaw]}>{markdown}</Markdown>
)
显示等效的 JSX
<div className="note">
<p>
Some <em>emphasis</em> and <strong>strong</strong>!
</p>
</div>
注意:Markdown 中的 HTML 仍然受 CommonMark 中 HTML 的运作方式的限制。确保在包含 Markdown 的块级 HTML 周围使用空行!
附录 B:组件
您还可以更改来自 Markdown 的内容:
<Markdown
components={{
// Map `h1` (`# heading`) to use `h2`s.
h1: 'h2',
// Rewrite `em`s (`*like so*`) to `i` with a red foreground color.
em(props) {
const {node, ...rest} = props
return <i style={{color: 'red'}} {...rest} />
}
}}
/>
组件中的键是您使用 Markdown 编写的元素的 HTML 对应项(例如,h1 对应 # 标题)。
在 Markdown 中,通常包括以下元素:a、blockquote、br、code、em、h1、h2、h3、h4、h5、h6、hr、img、li、ol、p、pre、strong 和 ul。
借助 remark-gfm,您还可以使用 del、input、table、tbody、td、th、thead 和 tr。
其他 remark 或 rehype 插件,如果添加了对新结构支持,同样可以与 react-markdown 配合使用。
传递的属性是您可能期望的:a(链接)将获得 href(以及 title)属性,而 img(图片)将获得 src、alt 和 title 等。
每个组件都会接收到一个 node。
这是原始的 Element 来自 hast 元素,将被转换成 React 元素。
附录 C:Markdown(和 JSX)中的行结束
您可能会遇到 Markdown 和 JSX 中行结束的处理问题。 我们建议如下,这将解决所有行结束问题:
// If you write actual markdown in your code, put your markdown in a variable;
// **do not indent markdown**:
const markdown = `
# This is perfect!
`
// Pass the value as an expression as an only child:
const result = <Markdown>{markdown}</Markdown>
👆 这有效。
继续阅读了解哪些不行以及原因。
你可能会尝试直接在 JSX 中编写 Markdown 并发现它无法工作:
<Markdown>
# Hi
This is **not** a paragraph.
</Markdown>
这是因为在使用 JSX 时,空白字符(包括行结束符)会被折叠为单个空格。 因此,上述示例等价于:
<Markdown> # Hi This is **not** a paragraph. </Markdown>
相反,要将 Markdown 传递给 Markdown,您可以使用一个表达式:
配合模板字符串:
<Markdown>{`
# Hi
This is a paragraph.
`}</Markdown>
模板字面量存在另一个潜在问题,因为它们会保留其中的空白(包括缩进)。 这意味着以下内容不会变成标题:
<Markdown>{`
# This is **not** a heading, it’s an indented code block
`}</Markdown>
安全性
默认情况下,使用 react-markdown 是安全的。
如果覆盖 urlTransform 为不安全的值,将会使你面临 XSS 攻击向量。
此外,你使用的 remarkPlugins、rehypePlugins 和 components 可能也不安全。
为了确保内容即使在插件处理后也完全安全,请使用 rehype-sanitize。
它允许你定义自己的允许与禁止的规则。
相关项目
MDX— Markdown 中的 JSXremark-gfm— 添加对 GitHub 风格 Markdown 的支持react-remark— 基于钩子的替代方法rehype-react— 将 HTML 转换为 React 元素
贡献指南
查看 contributing.md 在 remarkjs/.github 以了解如何开始贡献。
查看 support.md 以获取帮助的方式。
本项目有 行为准则。 通过与这个仓库、组织或社区互动,你同意遵守其条款。