react-markdown是一款github上开源的适用于react的markdown组件,可以基本实现markdown的功能,且可以根据自己的实际应用定制remark组件。官方给出该组件的好处是:

The two main reasons are that they often rely on dangerouslySetInnerHTML or have bugs with how they handle markdown. react-markdown uses a syntax tree to build the virtual dom which allows for updating only the changing DOM instead of completely overwriting. react-markdown is 100% CommonMark (optionally GFM) compliant and has extensions to support custom syntax.

本文将介绍react-markdown的配置,以及部分实用组件的配置和介绍,包括Katex、syntax-highlighter、Gfm以及TOC。react-markdown的源码和完全介绍请移步https://github.com/remarkjs/react-markdown

react-markdown基本配置

  1. 使用npm install react-markdown引入react-markdown包,注意该组件的旧版本和新版本使用配置差别较大,我目前为v7.1.0

  2. 一个简单的hello world:

    import React from 'react'
    import ReactMarkdown from 'react-markdown'
    import ReactDom from 'react-dom'
    
    ReactDom.render(<ReactMarkdown># Hello, *world*!</ReactMarkdown>, document.body)
    

    如此以来,简单的react-markdown就配置好啦!但是这种配置支持的语法较少,如表格、代码高亮、数学公式等均不支持,且无法实现同步预览效果。

同步预览配置

我使用的是redux来控制状态,当然也可以使用dva或者其他方式。

MdEditorComponent.js

import React, { Component } from 'react'
import styles from './MdEditorComponent.module.css'
import { connect } from 'react-redux'
import { editMarkdown } from '../../../redux/sync-actions'
import ReactMarkdownComponent from '../article-component/ReactMarkdownComponent'

const DEFAULT_SIZE = 500 //默认代码块高度

class MdEditorComponent extends Component {
  constructor (props) {
    super(props)
    this.state = {
      textareaHeight: DEFAULT_SIZE
    }
  }

  componentDidMount () {
    window.addEventListener('resize', this.handleResize) //监听resize
  }

  handleResize = e => {

  }

  //修改内容状态
  handleInputMarkdown = e => {
    let previewHeight = this.preview.scrollHeight
    let textareaHeight = e.target.scrollHeight
    let height = Math.max(previewHeight, textareaHeight, DEFAULT_SIZE)
    this.setState({ textareaHeight: height })
    this.props.editMarkdown(e.target.value)
  }

  //当鼠标选中textarea时
  handleKeyPressMarkdown = e => {
    let previewHeight = this.preview.scrollHeight
    let textareaHeight = e.target.scrollHeight
    let height = Math.max(previewHeight, textareaHeight, DEFAULT_SIZE)
    //下面是自定义的特殊按键触发的效果
    if (e.keyCode === 9) {
      e.preventDefault()
      this.props.editMarkdown(this.props.markdown + '\t')
    } else if (e.keyCode === 13)
      return false
    else if (e.keyCode === 17) { //left ctrl
      this.setState({ textareaHeight: height })
    } else if (e.keyCode === 8) { //backspace
      this.setState({ textareaHeight: height })
    } else if (e.keyCode === 192) { // ~
      // e.preventDefault()
    }
  }

  render () {
    return (
      <div className={styles.container} style={{
        height: this.state.textareaHeight //设置默认高度
      }}>
        <div className={styles.input}>
          <textarea style={{ border: '0px' }} spellCheck={false} value={this.props.markdown}
                    onKeyDown={this.handleKeyPressMarkdown}
                    onChange={this.handleInputMarkdown}/>
        </div>
        <div ref={div => this.preview = div} className={styles.md}>
          <ReactMarkdownComponent content={this.props.markdown}/>
        </div>
      </div>
    )
  }
}

//使用redux来控制状态
MdEditorComponent = connect(
  ({ $BlogState: { mdContent } }) => ({ markdown: mdContent }),
  { editMarkdown }
)(MdEditorComponent)

export default MdEditorComponent

reducer.js

export function $BlogState (state = initBlogState, { data, type }) {
  switch (type) {
    case EDIT_MARKDOWN:
      return { ...state, mdContent: data }
    default:
      return state
  }
}

initBlogState.js

初始的显示内容

export const initBlogState = {
  mdContent: `# Markdown Online Editor
## Subtitle

common content

### Little title

    Code Block
  
### Code Syntax Highlight
~~~js
console.log('hello randyzhang')
~~~
`,
}

action-types.js

export const EDIT_MARKDOWN = 'edit_markdown'

sync-actions.js

export const editMarkdown = data => actionFactory(EDIT_MARKDOWN, data)

store.js

import { applyMiddleware, combineReducers, createStore } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
import { $BlogState } from './reducers'

const store = createStore(
  combineReducers({
    $BlogState,
  }),
  composeWithDevTools(applyMiddleware(thunk))
)

export default store

remark-Gfm配置

配置好后即可使用表格、删除线、任务列表、引用等操作。需要使用npm install remark-Gfm引入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
)

展示的效果为:

A paragraph with emphasis and strong importance.

A block quote with strikethrough and a URL: https://reactjs.org.

  • Lists
  • todo
  • done

A table:

ab

syntax-highligh配置

使用该组件可以实现代码块的彩色,需要使用npm install react-syntax-hightlighter --save来引入react-syntax-highlighter包。

这里components中间的部分主要是针对输入的代码块语言匹配相应的代码高亮,如果非匹配成功则直接使用code形式展示。

import React from 'react'
import ReactDom from 'react-dom'
import ReactMarkdown from 'react-markdown'
import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter'
import {dark} from 'react-syntax-highlighter/dist/esm/styles/prism'

const markdown = `Here is some JavaScript code:

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

ReactDom.render(
  <ReactMarkdown
    children={markdown}
    components={{
      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>
        )
      }
    }}
  />,
  document.body
)

rehype-katex和remark-math的配置

使用该组件可以轻松的翻译输入的数学公式,输入npm install remark-math rehype-katex引入相应的包。

注意:需要使用katex.css来展示相应的效果,否则会出现BUG

需要在index.html中添加上下列的话并下载包:

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

const markdown = ` L = \frac{1}{2} \rho v^2 S C_L`

ReactDom.render(
  <ReactMarkdown
    children={markdown}
    remarkPlugins={[remarkMath]}
    rehypePlugins={[rehypeKatex]}
  />,
  document.body
)

展示的效果为:

L = 1 2 ρ v 2 S C L L = \frac{1}{2} \rho v^2 S C_L L=21ρv2SCL

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的配置

如果你trusted environment (you trust the markdown),可以使用该配置使markdown支持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
)

个性化组件配置

有时候我们需要修改一些颜色、大小等,则可以按照一下规则进行修改:

<ReactMarkdown
  components={{
    // Map `h1` (`# heading`) to use `h2`s.
    h1: 'h2',
    // Rewrite `em`s (`*like so*`) to `i` with a red foreground color.
    em: ({node, ...props}) => <i style={{color: 'red'}} {...props} />
  }}
/>

总结

react-markdown作为一款适用于react的markdown组件,使用非常方便,且丰富的remark和rehype可以扩展很多功能,自己也可以定义remark组件,推荐大家来使用。

如果有好的使用方法或者更适合的组件也可以告诉我,互相学习和进步~

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐