交互式md文档渲染实现

🧑‍💻 写在开头

点赞 + 收藏 === 学会 🤣🤣🤣

本篇是本人在做一个交互式文章博客的一种尝试,我希望能直接将自己编写的组件渲染到md文档中,并且能够交互,类似组件库文档的渲染,简单实现了一个demo,可以将已经注册的组件通过下拉选框的方式便捷插入文档中。

🥑 你能学到什么?

希望你在阅读本文后不会觉得浪费了时间。如果你跟着学习,你将会掌握:

  • 如何在md中注入组件且可交互
  • 如何实现一个灵活的组件注册系统通过 ComponentRegistry 管理组件
  • 使用 react-markdown实现Markdown解析自定义渲染逻辑
  • 如何将静态内容转变为动态交互式内容

🍐 效果

在线体验

🍒 实现思路

组件注册表

  • 提供一个中央注册中心,管理所有可用的交互式组件
  • 确保组件注册的唯一性和全局可访问性
  • 方便在 Markdown 渲染时快速查找和使用已注册的组件
  • 在应用启动时注册默认组件
ts 复制代码
import { ComponentRegistry } from '../types/components';

class ComponentRegistryService {
  private static instance: ComponentRegistryService;
  private registry: ComponentRegistry = {};

  private constructor() {}

  public static getInstance(): ComponentRegistryService {
    if (!ComponentRegistryService.instance) {
      ComponentRegistryService.instance = new ComponentRegistryService();
    }
    return ComponentRegistryService.instance;
  }

  public register(id: string, config: ComponentRegistry[string]): void {
    this.registry[id] = config;
  }

  public get(id: string): ComponentRegistry[string] | undefined {
    return this.registry[id];
  }

  public getAll(): ComponentRegistry {
    return { ...this.registry };
  }
}

export const componentRegistry = ComponentRegistryService.getInstance();

全局注册组件

组件注册 : 通过registerComponents函数,将这些组件都注册到组件注册表中。每个组件注册时包含以下信息:

  • type: 组件类型名称
  • component: 组件本身
  • props: 组件的默认属性

main.tsx中注册全部组件

js 复制代码
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import { registerComponents } from './services/registerComponents'

// 注册所有交互式组件
registerComponents();

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

实现Markdown解析自定义渲染逻辑

  • 使用react-markdown作为基础的Markdown渲染器
  • 使用rehype-raw插件来支持原始HTML的渲染
  • 引入componentRegistry来管理交互式组件

自定义渲染规则 : 通过customComponents定义了特殊的div渲染规则:

  • 当遇到带有data-component属性的div时:

    • 获取data-component的值作为组件ID
    • 调用renderComponent渲染对应的交互式组件
    • 将渲染好的组件包装在div中返回
  • 对于普通的div,保持原样渲染

这样的设计让我们可以在Markdown中嵌入交互式组件,例如:

ini 复制代码
文本

<div data-component="counter1"></div>

文本

<div data-component="button1"></div>

当渲染这样的Markdown时,counter1button1会被替换成我们之registerComponents.ts中注册的实际交互式组件,核心代码如下

tsx 复制代码
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import { MarkdownComponentProps } from '../types/components';
import type { Components } from 'react-markdown';
import type { HTMLAttributes, DetailedHTMLProps } from 'react';
import { componentRegistry } from '../services/ComponentRegistry';


export const MarkdownRenderer = ({ content }: MarkdownComponentProps) => {
  const renderComponent = (id: string) => {
    const componentConfig = componentRegistry.get(id);
    if (!componentConfig) {
      console.warn(`Component with id ${id} not found in registry`);
      return null;
    }
    const componentFunction = componentConfig.component;
    const props = componentConfig.props;
    if (!componentFunction) {
      console.warn(`Component type ${componentConfig.type} not found in componentMap`);
      return null;
    }

    return componentFunction(props);
  };

  const customComponents: Components = {
    div: (props: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> & { 'data-component'?: string }) => {
      const { 'data-component': dataComponent, ...rest } = props;
      console.log('Rendering div with props:', props);
      
      if (dataComponent) {
        const component = renderComponent(dataComponent);
        if (component) {
          return <div {...rest}>{component}</div>;
        }
      }
      
      return <div {...rest} />;
    }
  };

  return (
    <div className="markdown-content">
      <ReactMarkdown
        rehypePlugins={[rehypeRaw]}
        components={customComponents}
      >
        {content}
      </ReactMarkdown>
    </div>
  );
};

🍎后序

后面我们可以引入一些开源md编辑器并集成到博客网站上去

相关推荐
noravinsc1 小时前
This dependency was not found: * @logicflow/core/dist/LogicFlow.css
前端·css
独莫子凡1 小时前
vue项目 Axios创建拦截器
前端·javascript·vue.js
LLLuckyGirl~1 小时前
webpack配置之---output.publicPath
前端·webpack·node.js
Mr-K1 小时前
ElementUI el-popover弹框背景色设置
前端·vue.js·elementui
iiFrankie1 小时前
使用 npx tailwindcss init 时发生 npm error could not determine executable to run 错误
前端·npm·node.js
灵境引路人2 小时前
【前端】几种常见的跨域解决方案&代理的概念
前端
万物得其道者成2 小时前
前端项目零停机部署方案与最佳实践
前端
嘘嘘出差2 小时前
DeepSeek渣机部署编程用的模型,边缘设备部署模型
前端·人工智能·python·深度学习·语言模型·自然语言处理·deepseek
刘懿儇2 小时前
Next.js国际化:next-i18next
前端·javascript·react.js
我不当帕鲁谁当帕鲁2 小时前
arcgis for js实现层叠立体效果
前端·javascript·arcgis