react-markdown的使用

react-markdown的使用

安装

npm i react-markdown

基本使用

import ReactMarkdown from 'react-markdown'

const markdownData = `
    ### test header
`

<RactMarkdown>
    {markdownData}
</ReactMarkdown>

结合react-syntax-highlighter使得代码拥有语法高亮

npm i react-syntax-highlighter #安装react-syntax-highlighter
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 = `
    ### test header
`

<RactMarkdown
    components={{
        code: ({ children = [], className, ...props }) => {
            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>)
        }
    }}
>
    {markdownData}
</ReactMarkdown>

结合mermaid(可以显示流程图)和katex(可以显示数学公式)

思路就是通过classname不同,判断code的类型,markdown就将 ```xxx 解析到classname中,并给一个language-xxx的classname,再通过classname使用不同的渲染器即可实现,下面将code函数的代码单独抽离出来

import { 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'

export default function SpecialCode({ inline, ...extra }: CodeProps) {
  if (inline) return <RenderInline {...extra} />

  return <RenderBlock {...extra} />
}

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>
}

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("")
}

再修改ReactMarkdown的代码如下

import ReactMarkdown from 'react-markdown'

const markdownData = `
    ### test header
`

<RactMarkdown
    components={{
        code: SpecialCode
    }}
>
    {markdownData}
</ReactMarkdown>
posted @ 2023-03-30 14:29  如戏一场  阅读(1726)  评论(0编辑  收藏  举报