react-markdown
安装
npm i react-markdown
基本使用
- 表格、代码高亮、数学公式等均不支持
import React from 'react'
import ReactMarkdown from 'react-markdown'
import ReactDom from 'react-dom'
const markdownData = `
### test header
`
<RactMarkdown>
{markdownData}
</ReactMarkdown>
ReactDom.render(
<ReactMarkdown># Hello, *world*!</ReactMarkdown>,
document.body
)
remark-gfm多功能
npm install remark-Gfm
- 配置后,可使用表格、删除线、任务列表、引用等操作。
import React from 'react'
import ReactMarkdown from 'react-markdown'
import ReactDom from 'react-dom'
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 |
| - | - |
`
ReactDom.render(
<ReactMarkdown children={markdown} remarkPlugins={[remarkGfm]} />,
document.body
)
highlighter语法高亮
- 结合react-syntax-highlighter使得代码拥有语法高亮
npm i react-syntax-highlighter
使用
import React from "react";
import ReactMarkdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { coldarkCold } from "react-syntax-highlighter/dist/esm/styles/prism";
const markdownData = `
~~~js
console.log('markdownData!')
~~~
`;
const code = ({ node, inline, className, children=[], ...props }) => {
const match = /language-(\w+)/.exec(className || "");
return !inline && match ? (
<SyntaxHighlighter
children={String(children).replace(/\n$/, '')}
style={dark}
language={match[1]}
PreTag="div"
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
)
};
const Index = () => {
return (
<ReactMarkdowncomponents={{code: code}}>
{markdownData}
</ReactMarkdown>
);
};
export default Index
katex数学公式
npm install remark-math rehype-katex
// <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.13.13/dist/katex.min.css" integrity="sha384-RZU/ijkSsFbcmivfdRBQDtwuwVqK7GMOw6IMvKyeWL2K5UAlyp6WonmB8m7Jd0Hn" crossorigin="anonymous">
import React from 'react'
import ReactDom from 'react-dom'
import ReactMarkdown from 'react-markdown'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import "katex/dist/katex.css";
const markdown = ` L = \frac{1}{2} \rho v^2 S C_L`
ReactDom.render(
<ReactMarkdown
children={markdown}
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
/>,
document.body
)
remark-toc目录生成
- 用于自动生成markdown的目录,仅需要在开头使用
## Table of Content
,即可以自动生成。 npm install remark-toc
import React from 'react'
import ReactDom from 'react-dom'
import ReactMarkdown from 'react-markdown'
import remarkToc from 'remark-toc'
ReactDom.render(
<ReactMarkdown
children={markdown}
remarkPlugins={[remarkToc]}
/>,
document.body
)
rehype-raw HTML支持
npm install rehype-raw
import React from 'react'
import ReactDom from 'react-dom'
import ReactMarkdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
const input = `<div class="note">
Some *emphasis* and <strong>strong</strong>!
</div>`
ReactDom.render(
<ReactMarkdown rehypePlugins={[rehypeRaw]} children={input} />,
document.body
)
自定义映射样式
- html 元素映射fn返回组件或元素
<ReactMarkdown
components={{
h1: 'h2',
em: ({node, ...props}) => <i style={{color: 'red'}} {...props} />
p: () => <p style={{color: 'red'}} {...props} />
}}
/>
流程图\数学公式
npm i mermaid
npm i katex
- 最终使用
import ReactMarkdown from 'react-markdown'
const markdownData = `
### test header
`
<RactMarkdown
components={{
code: SpecialCode
}}
>
{markdownData}
</ReactMarkdown>
思路就是通过classname不同,判断code的类型,markdown就将 ```xxx 解析到classname中,并给一个language-xxx的classname,再通过classname使用不同的渲染器即可实现,下面将code函数的代码单独抽离出来
import React, { useRef, useEffect, useMemo, useCallback, useState } from "react";
import { CodeProps } from "react-markdown/lib/ast-to-react";
import mermaid from "mermaid";
import katex from "katex";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { coldarkCold } from "react-syntax-highlighter/dist/esm/styles/prism";
import "katex/dist/katex.css";
// <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.13.13/dist/katex.min.css" integrity="sha384-RZU/ijkSsFbcmivfdRBQDtwuwVqK7GMOw6IMvKyeWL2K5UAlyp6WonmB8m7Jd0Hn" crossorigin="anonymous">
function randomId() {
return parseInt(String(Math.random() * 1e15), 10).toString(36);
}
function getCode(arr: React.ReactNode[] = []): string {
return arr
.map(_dt => {
const dt = _dt as any;
if (typeof dt === "string") {
return dt;
}
if (dt.props && dt.props.children) {
return getCode(dt.props.children);
}
return false;
})
.filter(Boolean)
.join("");
}
function RenderInline({ children = [] }: Omit<CodeProps, "inline">) {
const txt = children[0] || "";
if (typeof txt === "string" && /^\$\$(.*)\$\$/.test(txt)) {
const html = katex.renderToString(txt.replace(/^\$\$(.*)\$\$/, "$1"), { throwOnError: false });
return (
<code
style={{ backgroundColor: "rgb(227, 234, 242)", padding: "0.2rem", borderRadius: "0.2rem" }}
dangerouslySetInnerHTML={{ __html: html }}
/>
);
}
return <code>{txt}</code>;
}
function RenderBlock({ children = [], className, ...props }: Omit<CodeProps, "inline">) {
const markId = useRef(`mark${randomId()}`);
const code = getCode(children);
const [transformCode, setTransformCode] = useState("");
const codeType = useMemo(() => {
if (typeof code !== "string" || typeof className !== "string") return;
if (/^language-mermaid/.test(className.toLocaleLowerCase())) {
return "mermaid";
} else if (/^language-katex/.test(className.toLocaleLowerCase())) {
return "katex";
}
}, [code, className]);
const renderMermaid = useCallback(async (code: string) => {
const { svg } = await mermaid.render(markId.current, code);
setTransformCode(svg);
}, []);
const renderKatex = useCallback(async (code: string) => {
const strCode = katex.renderToString(code, { throwOnError: false });
setTransformCode(strCode);
}, []);
useEffect(() => {
if (codeType === "mermaid") {
renderMermaid(code);
} else if (codeType === "katex") {
renderKatex(code);
}
}, [code, codeType]);
if (codeType === "mermaid") {
return (
<div className='code-block' style={{ textAlign: "center" }}>
<code dangerouslySetInnerHTML={{ __html: transformCode }} />
</div>
);
} else if (codeType === "katex") {
return <code className='code-block' dangerouslySetInnerHTML={{ __html: transformCode }} />;
}
const match = /language-(\w+)/.exec(className || "");
return (
<SyntaxHighlighter
language={match?.[1]}
showLineNumbers={true}
style={coldarkCold as any}
PreTag='div'
className='syntax-hight-wrapper'
{...props}>
{children as string[]}
</SyntaxHighlighter>
);
}
const SpecialCode = ({ inline, ...extra }: CodeProps) => {
if (inline) return <RenderInline {...extra} />;
return <RenderBlock {...extra} />;
};
export default SpecialCode;
扩展:markdown 编辑器