🧑💻 写在开头
点赞 + 收藏 === 学会 🤣🤣🤣
本篇是本人在做一个交互式文章博客的一种尝试,我希望能直接将自己编写的组件渲染到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时,counter1
和button1
会被替换成我们之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编辑器并集成到博客网站上去