基于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显示器完成:

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

T.Y.Bao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值