富文本里解析vue、react组件
react
封装的渲染富文本的组件: RenderRtf.tsx
import { useState, useEffect, useRef } from "react";
import parse from "html-react-parser";
import ReactDOM from "react-dom/client";
import Hello from "./Hello";
// 这里注册富文本中包含的自定义组件(也可以外部注册)
const regCompPublic = {
Hello,
};
// 渲染富文本
const RenderRtf = (props) => {
// 组件名字处理
const regCompTemp = { ...regCompPublic, ...props["regComp"] };
const regComp = {};
for (const key in regCompTemp) {
const keyStr = key.toLowerCase();
regComp[keyStr] = regCompTemp[key];
}
// 子实例的根节点
const [root, setRoot] = useState<any>();
// 子实例的根节点的挂载点
const wrappNode = useRef(null);
// 子实例渲染
const rootRender = () => {
if (!root) {
console.warn("没有实例对象,此次实例无法重新render");
return false;
}
root.render(
parse(props.content, {
replace(domNode: any) {
if (Object.keys(regComp).indexOf(domNode.name) > -1) {
const Cmp = regComp[domNode.name];
return <Cmp {...domNode.attribs} />;
} else {
return domNode;
}
},
})
);
};
// 传入的内容更改 就需要重新渲染
useEffect(() => {
if (root) {
rootRender();
}
}, [props]);
// 挂载第二(子)实例
useEffect(() => {
if (wrappNode.current && !root) {
const instanceRoot = ReactDOM.createRoot(wrappNode.current);
setRoot(instanceRoot);
rootRender();
}
}, [wrappNode]);
return (
<>
<div className="wrapp" ref={wrappNode}></div>
</>
);
};
export default RenderRtf;
页面中使用: App.tsx
import { useState } from "react";
import RenderRtf from "./components/RenderRtf";
import MarkdownIt from "markdown-it";
const md = MarkdownIt({
html: true,
linkify: true,
typographer: true,
});
function App() {
const [ipt, setIpt] = useState("你好 <Hello/>");
const onInput = (e: any) => {
setIpt(e.target.value);
};
return (
<>
<textarea value={ipt} onInput={onInput}/>
<RenderRtf content={md.render(ipt)} />
</>
);
}
export default App;
html
基于此,vue我也实现了
封装的渲染富文本的组件: render-rtf.vue
<script setup lang="ts">
import { onMounted, ref, watch } from "vue";
import Hello from "@/components/HelloWorld.vue";
import * as Vue from "vue/dist/vue.esm-bundler.js";
const props = defineProps(["content"]);
let app: any = null;
const renRender = () => {
if (!showRtf.value) {
console.warn("没有子实例对象的挂载真实node,此次实例无法重新render");
return false;
}
// 不同于react,vue对外没有暴漏render函数,所以无法手动调用,只能更新整个实例来渲染
if (app) {
app.unmount();
}
showRtf.value.innerHTML = props.content;
app = Vue.createApp({
components: { //这里组测富文本中包含的vue组件
Hello,
},
});
app.mount(showRtf.value);
};
onMounted(() => {
renRender();
});
watch(
() => props.content,
(newVal) => {
renRender();
}
);
const showRtf = ref();
</script>
<template>
<div ref="showRtf"></div>
</template>
这里是使用 App.vue
<script setup lang="ts">
import {createApp} from 'vue';
import { ref, watch, computed } from "vue";
import MarkdownIt from "markdown-it";
import RenderRtf from '@/components/render-rtf.vue';
const md = MarkdownIt({
html: true,
linkify: true,
typographer: true,
});
const ipt = ref("哈哈<Hello/>");
</script>
<template>
<textarea v-model="ipt"></textarea>
<render-rtf :content="md.render(ipt)"/>
</template>
结语
这样我们的markdwon不但可以使用官方规定的语法,还可以显示自定义组件。
这。。。这不就是第二个语雀了嘛?!