天天看點

react解析md檔案

效果

react解析md檔案
react解析md檔案

需求

因為項目最後是對外開放的,是以在項目中有個文檔中心,裡面有一些使用者手冊、開發文檔等展示需求。同時支援文章目錄點選。

分析

第一時間想到的就是解析

md

檔案了,文法簡單,滿足大部分寫作要求,市面上也很有很多成熟的解析方案。

依賴項

這裡是使用了

react-markdown

github-markdown-css

這兩個庫,一個是解析

md

檔案,一個是github的

md

文檔樣式。

具體

解析md檔案(大概)
import React, { useState, useEffect } from 'react';
import ReactMarkdown from 'react-markdown/with-html';

import 'github-markdown-css/github-markdown.css'

const articles = {
  '1': '/developer_guide.md',
  '2': '/user_manual.md'
}
const NormalTest: React.FC<any> = () => {
  const [currentArticle, setCurrentArticle] = useState<{ url: string, content: any }>({ url: '', content: '' });
      
  // 初始為開發文檔
  useEffect(() => {
    changeCurrentArticle(articles['1'])
  }, [])
      
  // 更改目前文檔
  const changeCurrentArticle = async (url: string) => {
    const res = await fetch(url);
    const content = await res.text();
    setCurrentArticle({ ...currentArticle, content, url })
  }

   return (
     <>
       <ReactMarkdown
           className="markdown-body"
           source={currentArticle.content}
           escapeHtml={false}
        />
      </>
    )
};
           

如果頁面正确顯示,說明是解析成功

單獨解析目錄

這裡要用到

react-markdown

元件的

renderers

API,給

title

加上錨點

HeadBlock.tsx

import React from 'react';

const HeadBlock: React.FC<any> = (props) => {

  const { level, children } = props;
  const { nodeKey } = children[0].props;

  return (
    <>
      {React.createElement(`h${level}`, { className: 'article-nav', id: nodeKey, 'data-level': level }, children)}
    </>
  );
}

export default HeadBlock;
           

index.tsx

import HeadBlock from './HeadBlock';

<ReactMarkdown
    className="markdown-body"
    source={currentArticle.content}
    escapeHtml={false}
    renderers={{
    heading: HeadBlock
    }}
/>
           

完整版

import React, { useState, useEffect } from 'react';
import { Row, Col, Menu, Affix, Anchor } from 'antd';
import ReactMarkdown from 'react-markdown/with-html';
import { isEmpty } from "lodash";

import HeadBlock from './HeadBlock';

import 'github-markdown-css/github-markdown.css'
import './index.less';

const { Link } = Anchor;

const articles = {
  '1': '/developer_guide.md',
  '2': '/user_manual.md'
}


/**
 * 
 * @param lists 
 * 這裡隻做兩級處理
 */
export const navsToTree = (lists: any[]) => {
  if (isEmpty(lists)) return [];
  // 提取第一個level為最大level 後續比他大的一律為同級
  const maxLevel = lists[0].level;
  const newLists: any[] = [];
  lists.forEach((item: any) => {
    // 一級 同級
    if (item.level <= maxLevel) {
      newLists.push(item)
    } else {
      // 非同級
      if (newLists[newLists.length - 1].children) {
        newLists[newLists.length - 1].children.push(item)
      } else {
        newLists[newLists.length - 1].children = [item]
      }
    }
  })
  return newLists;
}



const NormalTest: React.FC<any> = () => {

  const [currentArticle, setCurrentArticle] = useState<{ url: string, content: any }>({ url: '', content: '' });

  const [treeNavs, setTreeNavs] = useState<any[]>([])

  // 初始為開發文檔
  useEffect(() => {
    // console.log(1);

    changeCurrentArticle(articles['1'])
  }, [])

  // 這裡是根據文檔修改進行擷取目錄
  useEffect(() => {
    /**
    *  擷取所有的文章标題
    */
    // console.log(currentArticle);

    const markdownNavs = document.querySelectorAll('.article-nav')
    const navs: any[] = [];
    markdownNavs.forEach((item: any) => {
      const level = item.getAttribute('data-level');
      const value = item.textContent;
      const nodeKey = item.id;
      navs.push({ level, value, nodeKey })
    })
    transArticleNavs(navs)

  }, [currentArticle.content])

  // 更改目前文檔
  const changeCurrentArticle = async (url: string) => {
    const res = await fetch(url);
    const content = await res.text();
    setCurrentArticle({ ...currentArticle, content, url })
  }

  // 書籍導航點選
  const menuOnClick = (e: any) => {
    const url = articles[e.key]
    changeCurrentArticle(url)
  }

  // 轉換為文章右側目錄
  const transArticleNavs = (navs: any) => {

    // 轉換為二級導航
    const treedevelopDocs = navsToTree(navs);
    setTreeNavs(treedevelopDocs)

  }

  return (
    <>
      <Row className='articles'>
        <Col flex='200px' className="articles-list">
          <Affix offsetTop={24}>
            <Menu defaultSelectedKeys={['1']} onClick={menuOnClick} theme='light'>
              <Menu.Item key="1">開發文檔</Menu.Item>
              <Menu.Item key="2">使用文檔</Menu.Item>
            </Menu>
          </Affix>

        </Col>
        <Col flex='1' className='articles-content'>
          <div className='articles-content_wrpper'>
            <ReactMarkdown
              className="markdown-body"
              source={currentArticle.content}
              escapeHtml={false}
              renderers={{
                heading: HeadBlock
              }}
            />
          </div>
        </Col>
        <Col flex='200px' className="articles-menu">
          <Affix offsetTop={20} >
            <Anchor style={{ width: 160 }}>
              {
                treeNavs.map((item: any) => {
                  if (item.children) {
                    return (
                      <Link href={`#${item.nodeKey}`} title={item.value} key={item.nodeKey}>
                        {
                          item.children.map((childItem: any) => (
                            <Link href={`#${childItem.nodeKey}`} title={childItem.value} key={childItem.nodeKey} />
                          ))
                        }
                      </Link>
                    )
                  } else {
                    return (
                      <Link href={`#${item.nodeKey}`} title={item.value} key={item.nodeKey} />
                    )
                  }
                })
              }
            </Anchor>
          </Affix>
        </Col>
      </Row>
    </>
  );
};

export default NormalTest;