如何将某些 HTML 中的 Vue 组件添加到另一个 Vue 组件?

Har*_*San 5 html javascript laravel vue.js vuejs2

我有一个巨大的动态 HTML 字符串,我将它加载到divVue 组件中。HTML 字符串本质上是来自 WYSIWYG 编辑器的内容。本来我就是v-html用来做这个的,还好。

但是,现在有些情况下我需要用实际的 Vue 组件替换部分 HTML 字符串,我不确定这样做的最佳方法。

例如,我可能在 HTML 字符串中有一些标记,如下所示:

||map:23||
Run Code Online (Sandbox Code Playgroud)

我想要做的是用一个 Vue 组件替换它,如下所示:

<map-component :id="23"></map-component>
Run Code Online (Sandbox Code Playgroud)

我尝试在 Laravel 中提前进行字符串转换,然后v-html在 Vue 组件中使用它来注入内容,但这似乎并没有加载 Vue 组件。

然后我尝试为 HTML 内容使用一个插槽,这确实有效,但它具有令人讨厌的副作用,即在 Vue 能够正确呈现之前在屏幕上显示一堆未格式化的 HTML 内容一两秒钟。

所以我的问题是:是否有另一种(更优雅的)方法来做到这一点?我在想,在 Vue 组件加载 HTML 内容后,我可以以某种方式找到||map:23||标记中的实例,然后用正确的 Vue 组件动态替换它们,但如果可能的话,我不知道如何;我在 Vue 文档中找不到任何内容。

有谁知道这是否可能?谢谢你。

Tur*_*ght 6

您可以使用Vue.compile来编译模板字符串(可以包含 vue 组件)。

然后你可以把它和一个有render()方法的组件结合起来,来渲染模板:

// this component will dynamically compile the html
// and use it as template for this component.
Vue.component("dynamic-html", {
  props: ["html"],
  computed: {
    template() {
      if(this.html)
        return Vue.compile(this.html).render;
      return null;
    }
  },
  render() {
    if(this.template)
      return this.template();
    return null;
  }
});
Run Code Online (Sandbox Code Playgroud)

这允许您渲染任意模板字符串,其中也可以包含 vue 组件:

// this component will dynamically compile the html
// and use it as template for this component.
Vue.component("dynamic-html", {
  props: ["html"],
  computed: {
    template() {
      if(this.html)
        return Vue.compile(this.html).render;
      return null;
    }
  },
  render() {
    if(this.template)
      return this.template();
    return null;
  }
});
Run Code Online (Sandbox Code Playgroud)

此外,您还可以使用它来将道具/事件处理程序传递给字符串中的组件:

<dynamic-html html="<some-component></some-component>">
</dynamic-html>
Run Code Online (Sandbox Code Playgroud)

(您需要使用$attrs虽然来访问道具,因为它们不在组件的props定义中dynamic-html

完整代码示例:

<!-- Passing down props -->
<dynamic-html
    html='<some-component :prop="$attrs.myprop"></some-component>'
    :myprop="12"
></dynamic-html>

<!-- passing down events -->
<dynamic-html
    html='<some-component @click="$emit('foo', $event)"></some-component>'
    @foo="doSomething"
></dynamic-html>
Run Code Online (Sandbox Code Playgroud)
// this component will dynamically compile the html
// into a vue component
Vue.component("dynamic-html", {
  props: ["html"],
  computed: {
    template() {
      if(this.html)
        return Vue.compile(this.html).render;
      return null;
    }
  },
  render() {
    if(this.template)
      return this.template();
    return null;
  }
});

Vue.component("red-bold-text", {
  props: ["text"],
  template: '<span class="red">{{text}}</span>'
});

new Vue({
  el: '#root',
  data: {
    html: null,
    myBoundVar: "this is bound from the parent component"
  },
  mounted() {
    // get the html from somewhere...
    setTimeout(() => {
      this.html = `
        <div>
          WELCOME!
          <red-bold-text text="awesome text"></red-bold-text>
          <red-bold-text :text="$attrs.bound"></red-bold-text>
          <button @click="$emit('buttonclick', $event)">CLICK ME</button>
        </div>
      `;
    }, 1000);
  },
  methods: {
    onClick(ev) {
      console.log("You clicked me!");
    }
  }
});
Run Code Online (Sandbox Code Playgroud)
.red { color: red; font-weight: bold; margin: 6px; }
Run Code Online (Sandbox Code Playgroud)

  • 我以为我很了解 Vue。我对编译方法的存在没有任何线索。Updooted。 (3认同)

Har*_*San 5

Turtlefight 的答案非常有帮助且完整,但对于任何寻求快速、简单答案的人来说,请使用如下文字component组件:is将包含 Vue 组件的 HTML 内容注入到动态组件中:

// htmlStrWithVueComponents is a large string containing HTML and Vue components.
<component
    :is="{
        template: `<div>${htmlStrWithVueComponents}</div>`
    }"
>
</component>
Run Code Online (Sandbox Code Playgroud)

以下是描述此技术的几个来源:


编辑:值得注意的是,component :is您能做的事情相当有限。您不能制作非常复杂的template字符串或添加的mounted方法等。

对于我的特定用例,因为我需要一些更复杂的东西,所以我最终选择了以下内容,这是上面更简单的答案和 Turtlefight 的答案之间的混合:

// This code goes within the parent component's mounted method or wherever is makes sense:
Vue.component('component-name-here', {
    // Can add much more complex template strings here.
    template: `
        <div class="someClass">
            ${content}
        </div>
    `,
    // Can add lifecycle hooks, methods, computed properties, etc.
    mounted: () => {
        // Code here
    }
});

const res = Vue.compile(`
    <component-name-here>
    </component-name-here>
`);

new Vue({
    render: res.render,
    staticRenderFns: res.staticRenderFns
}).$mount('dom-selector-for-dom-element-to-be-replaced-by-vue-component');
Run Code Online (Sandbox Code Playgroud)