react-markdown 英文文档-中文翻译

react-markdown

一个用于渲染 Markdown 的 React 组件。

功能亮点

  • 默认安全 (没有使用 dangerouslySetInnerHTML 或 XSS 攻击风险)
  • 组件 (传入你自己的组件来替代 <h2> 以渲染 ## hi
  • 插件(你可以选择使用众多插件)
  • 兼容性(100% 兼容 CommonMark,使用插件后100% 兼容 GFM)

目录

这是什么?

这个包是一个 React 组件,可以接收一个 Markdown 字符串,并将其安全地渲染为 React 元素。 你可以传入插件来改变 Markdown 的转换方式,并传入组件来代替普通的 HTML 元素。

什么时候应该使用它?

市面上有其他在 React 中使用 Markdown 的方式,那么为什么要使用这个呢? 主要有三个原因是:其他方式通常依赖于 dangerouslySetInnerHTML,处理 Markdown 时有错误,或不允许你将元素替换为组件。 react-markdown 构建了一个虚拟 DOM,因此 React 只替换变化的部分,从语法树开始。 得益于我们使用了 unified,特别是 remark 处理 Markdown 和 rehype 处理 HTML,这些是流行的插件化内容转换工具。

这个包专注于让初学者在 React 中安全地使用 Markdown。 当你熟悉 unified 时,可以手动使用基于 modern hooks 的替代方案 react-remarkrehype-react。 如果你想在 Markdown 文件内部 使用 JavaScript 和 JSX,请使用 MDX

安装

此包仅支持 ESM。 在 Node.js(版本 16+)中,使用 npm 安装:

sh 复制代码
npm install react-markdown

在 Deno 中使用 esm.sh

js 复制代码
import Markdown from 'https://esm.sh/react-markdown@9'

在浏览器中使用 esm.sh

html 复制代码
<script type="module">
  import Markdown from 'https://esm.sh/react-markdown@9?bundle'
</script>

使用

一个简单的 Hello World:

jsx 复制代码
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

jsx 复制代码
<h1>
  Hi, <em>Pluto</em>!
</h1>

这是一个展示如何使用插件的例子(remark-gfm,它增加了对脚注、删除线、表格、任务列表和直接 URL 的支持):

jsx 复制代码
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

jsx 复制代码
<p>
  Just a link: <a href="http://www.nasa.gov">www.nasa.gov</a>.
</p>

API

此包输出以下标识符:defaultUrlTransform。 默认导出为 Markdown

Markdown

用于渲染 Markdown 的组件。

参数
返回值

React 元素(JSX.Element)。

defaultUrlTransform(url)

使 URL 安全。

参数
  • url (string) --- URL
返回值

安全的 URL(string)。

AllowElement

过滤元素的函数(TypeScript 类型)。

参数
返回值

是否允许 elementboolean,可选)。

Components

将标签名映射到组件的映射(TypeScript 类型)。

类型
ts 复制代码
import type {Element} from 'hast'

type Components = Partial<{
  [TagName in keyof JSX.IntrinsicElements]:
    // 类组件:
    | (new (props: JSX.IntrinsicElements[TagName] & ExtraProps) => JSX.ElementClass)
    // 函数组件:
    | ((props: JSX.IntrinsicElements[TagName] & ExtraProps) => JSX.Element | string | null | undefined)
    // 标签名:
    | keyof JSX.IntrinsicElements
}>

ExtraProps

我们传递给组件的额外字段(TypeScript 类型)。

字段

Options

配置(TypeScript 类型)。

字段
  • allowElement (AllowElement, 可选) --- 过滤元素;先使用 allowedElements / disallowedElements
  • allowedElements (Array<string>, 默认:所有标签名)
  • disallowedElements (Array<string>, 默认:[]) --- 不允许的标签名;不能与 allowedElements 同时使用
  • children (string, 可选) --- Markdown 内容
  • className (string, 可选) --- 用这个类名包裹在一个 div
  • components (Components, 可选) --- 将标签名映射到组件
  • rehypePlugins (Array<Plugin>, 可选) --- 要使用的 rehype 插件 列表
  • remarkPlugins (Array<Plugin>, 可选) --- 要使用的 remark 插件 列表
  • remarkRehypeOptions (remark-rehypeOptions, 可选) --- 传递给 remark-rehype 的选项
  • skipHtml (boolean, 默认:false) --- 完全忽略 Markdown 中的 HTML
  • unwrapDisallowed (boolean, 默认:false) --- 提取(解包)不允许元素中的内容;通常情况下,比如说 strong 不被允许时,它和它的子元素都会被丢弃,使用 unwrapDisallowed 时,元素本身被其子元素替代
  • urlTransform (UrlTransform, 默认:defaultUrlTransform) --- 更改 URL

UrlTransform

转换 URLs 的函数(TypeScript 类型)。

参数
  • url (string) --- URL
  • key (string, 例如:'href') --- 属性名
  • node (hast 中的 Element) --- 待检查的元素
返回值

转换后的 URL(string,可选)。

示例

使用插件

这个示例展示了如何使用一个 remark 插件。 这里的例子是 remark-gfm,它增加了对删除线、表格、任务列表和直接 URL 的支持:

jsx 复制代码
import React from 'react'
import {createRoot} from 'react-dom/client'
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'

const markdown = `一个带有*强调*和**重要性**的段落。

> 一个带有 ~删除线~ 和 URL 的引用块:https://reactjs.org。

* 列表
* [ ] 待办
* [x] 完成

一个表格:

| a | b |
| - | - |
`

createRoot(document.body).render(
  <Markdown remarkPlugins={[remarkGfm]}>{markdown}</Markdown>
)

显示等效的 JSX

jsx 复制代码
<>
  <p>
    一个带有<em>强调</em>和<strong>重要性</strong>的段落。
  </p>
  <blockquote>
    <p>
      一个带有<del>删除线</del>和 URL 的引用块: 
      <a href="https://reactjs.org">https://reactjs.org</a>。
    </p>
  </blockquote>
  <ul className="contains-task-list">
    <li>列表</li>
    <li className="task-list-item">
      <input type="checkbox" disabled /> 待办
    </li>
    <li className="task-list-item">
      <input type="checkbox" disabled checked /> 完成
    </li>
  </ul>
  <p>一个表格:</p>
  <table>
    <thead>
      <tr>
        <th>a</th>
        <th>b</th>
      </tr>
    </thead>
  </table>
</>

使用带选项的插件

这个示例展示了如何使用插件并给它传递选项。 要做到这一点,使用一个数组,插件在第一位,选项在第二位。 remark-gfm 有一个选项可以仅允许使用双波浪线来表示删除线:

jsx 复制代码
import React from 'react'
import {createRoot} from 'react-dom/client'
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'

const markdown = '这个 ~不是~ 删除线,但是 ~~这个是~~!'

createRoot(document.body).render(
  <Markdown remarkPlugins={[[remarkGfm, {singleTilde: false}]]}>
    {markdown}
  </Markdown>
)

显示等效的 JSX

jsx 复制代码
<p>
  这个 ~不是~ 删除线,但是 <del>这个是</del>!
</p>

使用自定义组件(语法高亮)

这个示例展示了如何通过传递组件来覆盖元素的正常处理方式。 在这种情况下,我们使用了令人惊叹的 [react-syntax-highlighter][

使用自定义组件(语法高亮)

这个示例展示如何通过传递组件来覆盖元素的正常处理方式。在这个例子中,我们通过令人惊艳的 react-syntax-highlighter 来应用语法高亮,作者是 @conorhastings

jsx 复制代码
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'

// 你知道你可以在 markdown 中用波浪线代替反引号来表示代码吗?✨
const markdown = `这里有一些 JavaScript 代码:

~~~js
console.log('It works!')
~~~
`

createRoot(document.body).render(
  <Markdown
    children={markdown}
    components={{
      code({node, inline, className, children, ...props}) {
        const match = /language-(\w+)/.exec(className || '')
        return !inline && match ? (
          <SyntaxHighlighter
            style={dark}
            language={match[1]}
            PreTag="div"
            {...props}
          >
            {String(children).replace(/\n$/, '')}
          </SyntaxHighlighter>
        ) : (
          <code className={className} {...props}>
            {children}
          </code>
        )
      }
    }}
  />
)

显示等效的 JSX

jsx 复制代码
<>
  <p>这里有一些 JavaScript 代码:</p>
  <pre>
    <SyntaxHighlighter language="js" style={dark} PreTag="div">
      console.log('It works!')
    </SyntaxHighlighter>
  </pre>
</>

使用 remark 和 rehype 插件(数学公式)

这个示例展示如何通过 remark-math(一个语法扩展插件)来支持 Markdown 中的数学公式,以及如何使用 rehype-katex(一个转换插件)来渲染这些数学公式。

jsx 复制代码
import React from 'react'
import {createRoot} from 'react-dom/client'
import Markdown from 'react-markdown'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import 'katex/dist/katex.min.css' // `rehype-katex` 不会为你导入 CSS

const markdown = `升力系数($C_L$)是一个无量纲系数。`

createRoot(document.body).render(
  <Markdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
    {markdown}
  </Markdown>
)

显示等效的 JSX

jsx 复制代码
<p>
  升力系数 (
  <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>
  ) 是一个无量纲系数。
</p>

插件

我们使用 unified,特别是 remark 处理 Markdown 和 rehype 处理 HTML,这些是通过插件转换内容的工具。 这里有三种好方法来查找插件:

语法

react-markdown 默认遵循 CommonMark,它标准化了 markdown 实现之间的差异。 通过插件支持某些语法扩展。

我们在后台使用 micromark 进行解析。 查看它的文档以获取更多关于 markdown、CommonMark 和扩展的信息。

类型

此包使用 TypeScript 完全类型化。 它导出了额外的类型 AllowElementExtraPropsComponentsOptions 以及 UrlTransform

兼容性

由 unified 集团维护的项目与 Node.js 的维护版本兼容。

当我们发布一个新的主要版本时,我们会放弃对 Node.js 不再维护版本的支持。 这意味着我们尝试保持当前的发布线路,react-markdown@^9,与 Node.js 16 兼容。

他们在所有现代浏览器中都能工作(本质上:所有非 IE 11 的浏览器)。 您可以在项目中使用打包工具(如 esbuild、webpack 或 Rollup),并使用其选项(或插件)为旧版浏览器添加支持。

架构

rust 复制代码
                                                           react-markdown
         +----------------------------------------------------------------------------------------------------------------+
         |                                                                                                                |
         |  +----------+        +----------------+        +---------------+       +----------------+       +------------+ |
         |  |          |        |                |        |               |       |                |       |            | |
markdown-+->+  remark  +-mdast->+ remark 插件 +-mdast->+ remark-rehype +-hast->+ rehype 插件 +-hast->+ 组件 +-+->react 元素
         |  |          |        |                |        |               |       |                |       |            | |
         |  +----------+        +----------------+        +---------------+       +----------------+       +------------+ |
         |                                                                                                                |
         +----------------------------------------------------------------------------------------------------------------+

要理解这个项目做了什么,首先了解 unified 是很重要的:请阅读 unifiedjs/unified readme 文档(直到你遇到 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

jsx 复制代码
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">

一些*强调*和<strong>加粗</strong>!

</div>`

createRoot(document.body).render(
  <Markdown rehypePlugins={[rehypeRaw]}>{markdown}</Markdown>
)

显示等效的 JSX

jsx 复制代码
<div className="note">
  <p>
    一些<em>强调</em>和<strong>加粗</strong>!
  </p>
</div>

注意 :Markdown 中的 HTML 仍然受限于 CommonMark 中 HTML 的工作方式。 确保在包含 markdown 的块级 HTML 周围使用空行!

附录 B:组件

你还可以改变从 markdown 来的内容:

jsx 复制代码
<Markdown
  components={{
    // 将 `h1` (`# heading`) 映射为使用 `h2`。
    h1: 'h2',
    // 将 `em`s (`*like so*`) 重写为红色前景色的 `i`。
    em({node, ...rest}) {
      return <i style={{color: 'red'}} {...rest} />
    }
  }}
/>

components 中的键是你用 markdown 写作时对应的 HTML 元素(例如 h1 对应 # heading)。 通常在 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(图片)将获得 srcalttitle 等。

每个组件将接收一个 node 属性。 这是被处理的原始 mdasthast 节点。这可以用来访问未被传递为属性的信息。

如果你想完全控制所有 HTML 标签的表现,可以传递一个完整的组件映射。例如,你也许想要为所有的 h1h2h3 等设置相同的样式,或者将所有的 precode 转换为使用语法高亮的组件。

jsx 复制代码
<Markdown
  components={{
    h1: ({node, ...props}) => <h2 {...props} />,
    h2: ({node, ...props}) => <h2 {...props} />,
    h3: ({node, ...props}) => <h2 {...props} />,
    // ...
    pre: ({node, ...props}) => <div {...props} />,
    code: ({node, ...props}) => <CodeBlock {...props} />,
    // ...
  }}
/>

在这个例子中,所有的标题都会被渲染为 h2,并且 precode 会被转换为一个自定义的 CodeBlock 组件,你可以在里面实现语法高亮等功能。

在实际应用中,你可能还需要处理一些更复杂的情况,例如嵌套的元素或者带有特定属性的元素。这时候,访问原始的 mdast 或 hast 节点就非常有用了,因为你可以使用这些信息来决定如何渲染这些组件。

例如,你可能想要根据 mdast 节点中的 align 属性来决定如何渲染一个表格:

jsx 复制代码
<Markdown
  components={{
    // ...
    table: ({node, ...props}) => {
      const align = node.align || [];
      // 根据 align 来定制化你的表格渲染
      return <CustomTable align={align} {...props} />;
    }
    // ...
  }}
/>

最终的输出会完全依赖于你提供的组件和它们如何使用传递给它们的属性和节点信息。

不要忘记,如果你提供了一个自定义的组件来覆盖默认行为,你需要确保它能正确处理所有可能的输入。这包括处理任何必要的儿童组件,属性,以及遵循 React 的最佳实践,以确保高性能和兼容性。

相关推荐
半花11 分钟前
i18n国际语言化配置
前端
编程贝多芬14 分钟前
Promise 的场景和最佳实践
前端·javascript
Asort15 分钟前
JavaScript 从零开始(四):基础语法详解——从变量声明到数据类型的完全指南
前端·javascript
木木jio18 分钟前
前端大文件分片上传 —— 基于 React 的工程化实现
前端·javascript
南雨北斗21 分钟前
JS的对象属性存储器
前端
Lotzinfly22 分钟前
12个TypeScript奇淫技巧你需要掌握😏😏😏
前端·javascript·面试
开源之眼26 分钟前
React中,useState和useReducer有什么区别
前端
普郎特37 分钟前
"不再迷惑!用'血缘关系'彻底搞懂JavaScript原型链机制"
前端·javascript
可观测性用观测云1 小时前
前端错误可观测最佳实践
前端
恋猫de小郭1 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter