富文本里解析vue、react组件

发布时间 2023-12-03 00:41:07作者: 丁少华

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不但可以使用官方规定的语法,还可以显示自定义组件。
这。。。这不就是第二个语雀了嘛?!