【问题标题】:Rendering Vue 3 component into HTML string将 Vue 3 组件渲染为 HTML 字符串
【发布时间】:2021-09-19 18:34:32
【问题描述】:

我正在开发一个 vue.js 项目(版本 3)。我遇到了一种情况,我想将组件的渲染 HTML 视图用于当前组件的方法。

我在我的 Vue 项目中创建了以下 SVG 组件。

CircleWithTextSvg.vue

<template>
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" width="20" height="20">
        <g id="UrTavla">
            <circle cx="10" cy="10" r="10" stroke="gray" stroke-width="1" :fill="fill" />
            <text x="50%" y="50%" text-anchor="middle" stroke="black" stroke-width="1px" dy=".3em">{{text}}</text>
        </g>
    </svg>
</template>

<script>
    export default {
        props: {
            text: { 
                type: String, 
                default: "1"
            },
            fill: { 
                type: String, 
                default: "white"
            }
        }
    }
</script>

这个组件基本上渲染了一个里面有文字的圆圈。 如果我在主要组件的模板部分中使用此组件,如下所示,那么它可以正常工作(显然)

MainComponent.vue

<template>
    <circle-with-text-svg />
</template>

但我想将此 SVG 组件渲染输出作为选项发送给第三方。

实际用例:- 实际上,我创建了这个单独的组件以在我的传单地图上显示为标记。现在的问题是我想在 MainComponent 的方法中使用这个 SVG 组件,这样我就可以将它用作 L.divIcon 的一个选项

当我尝试以下操作时

export default {
    methods: {
        generateMap(element) {
            // ...
            let icon = L.divIcon({ 
                html: <circle-with-text-svg 
                    fill="'#D3D5FF'"
                    text="1" />, 
                iconSize: [10, 10]
            });
            // ...
        }
    }
}

然后它给出错误

对实验性语法“JSX 当前未启用”的支持

在react中,我们可以简单的正常使用另一个组件的模板里面的组件。但是我们如何在 vue.js 中实现这一点

通过查看错误,似乎 JSX 实验没有启用。

有人可以告诉我如何实现这一目标吗?

【问题讨论】:

  • 你不认为这会增加开销吗?因为我迭代了太长的列表,每个列表元素都会为 SVG 创建自己的 vue 应用程序实例。
  • 顺便说一句,为什么它不适用于 vue 3。这是 vue 2 的吗?
  • 是的,链接的答案适用于 Vue 2,需要稍作更改才能与 Vue 3 一起使用(主要是 createApp 而不是 new Vue),但原理是一样的 - 创建新应用程序,挂载它,检索呈现的 HTML。关于开销 - createApp 与模板中的渲染组件非常相似 - 应用程序和组件都是 Vue 的实例。
  • 如果您发布您对 vue 3 的答案会更好。我无法将 vue 2 应用程序转换为 vue 3。提前致谢

标签: javascript vue.js svg leaflet vuejs3


【解决方案1】:

好的,所以在 cmets 中我推荐了问题 How to get the compiled html content of a component in vuejs 的答案,它实际上是为 Vue 2 实现的

我很好奇这是否适用于 Vue 3,您可以在下面看到结果。以下是需要进行的更改:

  1. 明显的变化是将new Vue 替换为createApp 并使用global h() 而不是传入render()
  2. 在 Vue 2 中,您可以在没有参数的情况下调用主 Vue 实例的 $mount() 函数。这很有效,因为 Vue 创建了要挂载到内存中的 DOM 元素。在 Vue 3 中情况并非如此 - 您需要自己提供元素
  3. 在我的示例中没有使用的一个重大更改对于某些用例非常重要的是,在 Vue 3 中,使用 app.component() 在主应用程序实例中注册为全局的组件在 tempApp 中无法访问用于呈现 HTML。所有使用中的组件都必须在适当的实例中注册 - 请参阅migration guide

// We use component options object with string template
// In the proper Vue app (with Webpack and Vue SFC) this whole block can be replaced with "import CircleWithTextSvg from CircleWithTextSvg.vue"
const CircleWithTextSvg = {
  name: 'CircleWithTextSvg',
  props: {
    text: {
      type: String,
      default: "1"
    },
    fill: {
      type: String,
      default: "white"
    }
  },
  template: `
  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" width="20" height="20">
        <g id="UrTavla">
            <circle cx="10" cy="10" r="10" stroke="gray" stroke-width="1" :fill="fill" />
            <text x="50%" y="50%" text-anchor="middle" stroke="black" stroke-width="1px" dy=".3em">{{text}}</text>
        </g>
    </svg>
  `
}

const app = Vue.createApp({
  mounted() {
    console.log(this.getIconHtml('Hello!'))
  },
  methods: {
    getIconHtml(text, type) {
      const tempApp = Vue.createApp({
        render() {
          return Vue.h(CircleWithTextSvg, {
            text,
            type
          })
        }
      })

      // in Vue 3 we need real element to mount to unlike in Vue 2 where mount() could be called without argument...
      const el = document.createElement('div');
      const mountedApp = tempApp.mount(el)

      return mountedApp.$el.outerHTML
    }
  }
})

app.mount('#app')
<script src="https://unpkg.com/vue@3.1.4/dist/vue.global.js"></script>
<div id='app'>
</div>

注意 1:上面的代码旨在直接在 import 不可用的浏览器中运行。出于这个原因,我们使用 Vue 全局构建并使用例如 Vue.createAppVue.h 访问 Vue 全局 API。在常规的 Vue 应用程序中,您需要将这些函数导入为 import { createApp, h } from 'Vue'

注意 2:可以说,如果与 Leaflet 组件一起使用的 HTML 片段与您的 CircleWithTextSvg 组件一样简单,那么更简单和高效的方法是将它们定义为 Vue 组件而不是 template literals

【讨论】:

    【解决方案2】:

    我尝试了上述解决方案,但对我来说并没有让步,$el.outerHTML 感觉不对,并且由于我的应用程序其余部分的生命周期而不断抛出 null 和 undefined。 在 vue3 中,我这样做了。它仍然感觉不对。但是到达那里:)

    
    const getIconHtml = (item, element) => render(h(CircleWithTextSvg, {
      onSomeEmit: (ev) => {
        
      },
      text: item, //props
    }), element //element to render to
    )
    
    

    在我的问题中,我必须为第 3 方 js(visjs) 库返回一个模板

    所以我的使用方式是这样的。

    import {render, h} from 'vue';
    
    const itemTemplate = async (item, element) => render(h(bar, {
      onDelete: (ev) => {
        vis.removeItem(item.id)
      },
      item: item,
    }), element
    )
    
    //note this template key does not refer to vuejs template
      template: (item, element) => {
        if (!item) return;
        return itemTemplate(item, element);
      }
    
    
    

    使用这种方法需要考虑的事项。 从技术上讲,这会创建一个新的 vue 实例,因此您想要使用的任何其他应用程序、道具、组件、指令都可能需要手动注册到该组件。 一些全局变量可能会起作用,但由于一些deep 问题,我无法让指令起作用,

    【讨论】:

      猜你喜欢
      • 2021-06-17
      • 2018-12-03
      • 2022-12-20
      • 2019-03-26
      • 2022-10-06
      • 2020-03-14
      • 1970-01-01
      • 2021-01-20
      • 1970-01-01
      相关资源
      最近更新 更多