交互式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编辑器并集成到博客网站上去

相关推荐
難釋懷40 分钟前
Vue解决开发环境 Ajax 跨域问题
前端·vue.js·ajax
特立独行的猫a1 小时前
Nuxt.js 中的路由配置详解
开发语言·前端·javascript·路由·nuxt·nuxtjs
咸虾米1 小时前
在uniCloud云对象中定义dbJQL的便捷方法
前端·javascript
草梅友仁1 小时前
AI 图片文字翻译与视频字幕翻译工具推荐 | 2025 年第 23 周草梅周报
开源·github·aigc
梨子同志1 小时前
JavaScript Proxy 和 Reflect
前端·javascript
汤圆炒橘子1 小时前
状态策略模式的优势分析
前端
90后的晨仔1 小时前
解析鸿蒙 ArkTS 中的 Union 类型与 TypeAliases类型
前端·harmonyos
IT_陈寒1 小时前
Element Plus 2.10.0 重磅发布!新增Splitter组件
前端·人工智能·后端
挑战者6668881 小时前
vue入门环境搭建及demo运行
前端·javascript·vue.js
贩卖纯净水.1 小时前
Webpack的基本使用 - babel
前端·webpack·node.js