基于react-markdown组件自定义一个Markdown显示器

我们先安装 npm install --save react-markdown引入到项目中,npm官网有介绍用法。

react-markdown有很多属性可以自定义,完整代码如下:

import React from 'react';
import moment from "moment";
import ReactMarkdown from "react-markdown";
// 这里引入自定义组件
import {
    
    code, h1, h2, h3, h4, a, blockquote, li} from './Markdown'
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import 'katex/dist/katex.min.css' // `rehype-katex` does not import the CSS for you
import Link from "next/link";

const PostDetail = ({
     
     post}) => {
    
    

    return (
       		...
       		// tailwindcss
            <div className="lg:p-4 p-2">
                <ReactMarkdown
                    className=""
                    // 传入markdown文本内容
                    children={
    
    post.content}
                    remarkPlugins={
    
    [remarkGfm, remarkMath]}
                    rehypePlugins={
    
    [rehypeKatex]}
                    components={
    
    {
    
    
                        code: code,
                        h1: h1,
                        h2: h2,
                        h3: h3,
                        h4: h4,
                        a: a,
                        blockquote: blockquote,
                        li:li
                    }}
                />
            </div>
    );
};

export default PostDetail;

由上,我们的自定义组件样式都在Markdown.jsx中,下面来看看:

import React from 'react';
import {
    
    Prism as SyntaxHighlighter} from "react-syntax-highlighter";
// 注意,这里有坑
import {
    
    oneDark as codeStyle} from "react-syntax-highlighter/dist/cjs/styles/prism";

// 定义一个代码块样式,官网给出的案例
export const code = ({
     
     node, inline, className, children, ...props}) => {
    
    
    const match = /language-(\w+)/.exec(className || '')
    // 判断是行内代码,还是独立代码块
    return !inline && match ? (
        <SyntaxHighlighter
            children={
    
    String(children).replace(/\n$/, '')}
            style={
    
    codeStyle}
            language={
    
    match[1]}
            PreTag="div"
            {
    
    ...props}
        />
    ) : (
        <span className="text-sm mx-1" style={
    
    {
    
    color: '#c7254e', backgroundColor: '#f9f2f4', borderRadius: '2px'}}>
                                    {
    
    children}
                                </span>
        // <code className={className} {...props}>
        //     {children}
        // </code>
    )
}

// h1组件自定义
export const h1 = ({
     
     node, ...props}) => {
    
    
    return (
        <div className="text-2xl mt-5 mb-3 font-bold" {
    
    ...props} />
    );
};

export const h2 = ({
     
     node, ...props}) => {
    
    
    return (
        <div className="text-xl mt-3 mb-1 font-bold" {
    
    ...props} />
    );
};

export const h3 = ({
     
     node, ...props}) => {
    
    
    return (
        <div className="text-lg mt-2 mb-1 font-bold" {
    
    ...props} />
    );
};

export const h4 = ({
     
     node, ...props}) => {
    
    
    return (
        <div className="mt-2 mb-1 font-bold" {
    
    ...props} />
    );
};
// 链接组件自定义
export const a = ({
     
     node, ...props}) => {
    
    
    return (
        <a href={
    
    node.properties.href} target="_blank"
           className="text-blue-600 hover:text-blue-500 mx-1 rounded-sm hover:shadow-md font-serif underline cursor-pointer" {
    
    ...props} />
    );
};
// 引用组件自定义
export const blockquote = ({
     
     node, ...props}) => {
    
    
    return (
        <div className="my-2 bg-gray-300 shadow-md font-serif rounded-lg p-4" {
    
    ...props}/>
    );
};
// li组件自定义
export const li = ({
     
     node, ...props}) => {
    
    
	// 注意,li的父组件可能为无序的<ul></ul>和有序的<ol></ol>
	// 根据不同的父组件渲染不同组件
	// props中ordered属性判断是有序还是无序
	// 如果有序props中的index为序号索引,一般加一显示
    if (props.ordered) {
    
    
        return (

            <div className="flex shadow-md my-2 font-serif rounded-lg p-1">
                {
    
    props.index + 1}. <div className="ml-2" {
    
    ...props}/>
            </div>
        )
    }
	// 无序渲染一个svg
    return (
        <div className="flex items-center shadow-md my-2 font-serif rounded-lg p-1 ">
            <span>
                 <svg t="1669231142038"
                      onClick={
    
    () => {
    
    
                       window.open("https://www.mcdonalds.com.cn/")
                      }}
                      className="icon h-5 w-5 mr-2 hover:cursor-pointer hover:scale-105 transition duration-75"
                      viewBox="0 0 1024 1024" version="1.1"
                      xmlns="http://www.w3.org/2000/svg"
                      p-id="8368" width="200" height="200">
                <path>...</path>
            </svg>
            </span>

            <div {
    
    ...props}/>
        </div>
    );
};


注意,上面引入import {oneDark as codeStyle} from "react-syntax-highlighter/dist/cjs/styles/prism";有个坑。官网让我们引入import {dark} from 'react-syntax-highlighter/dist/esm/styles/prism',但是按照官网引入会报错,参见 StackOverflow : SyntaxError: Unexpected token ‘export’ in Next.js

另外,对于react-markdown组件中katex公式显示,存在版本bug,会导致公式显示乱码,且react报错如下:

Hydration failed because the initial UI does not match what was rendered on

经测试,是Next版本原因,我从 13.0.4 版本降到 13.0.0版本后,公式显示正常,react版本不变 : "react": "18.2.0","react-dom": "18.2.0"

这样,一个简单的markdown显示器完成:

在这里插入图片描述

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_41866717/article/details/128016238