在vuejs2数据中动态插入子组件(没有$compile或滥用v-html)

我想在一个不一定预定义的 HTML块中的任意点上动态插入新的vuejs组件.

这是一个有点人为的例子,它展示了我正在尝试做的事情:

Vue.component('child',{
  // pretend I do something useful
  template: '<span>--&gt;<slot></slot>&lt;--</span>'
})

Vue.component('parent',{
  data() {
    return {
      input: 'lorem',text: '<p>Lorem ipsum dolor sit amet.</p><p><i>Lorem ipsum!</i></p>'
    }
  },template: `<div>
      Search: <input type='text' v-model="input"><br>
      <hr>
      This inserts the child component but doesn't render it 
      or the HTML:
      <div>{{output}}</div>
      <hr>
      This renders the HTML but of course strips out the child component:
      <div v-html="output"></div>
      <hr>
      (This is the child component,just to show that it's usable here: 
      <child>hello</child>)
      <hr>
      This is the goal: it renders both the input html 
      and the inserted child components:
      Todo ¯\_(ツ)_/¯
    </div>`,computed: {
    output() {
      /* This is the wrong approach; what do I replace it with? */
      var out = this.text;
      if (this.input) {
        this.input = this.input.replace(/[^a-zA-Z\s]/g,'');
        var regex = new RegExp(this.input,"gi");
        out = out.replace(regex,'<child><b>' + this.input + '</b></child>');
      }
      return out;
    }
  }
});

new Vue({
  el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<div id="app">
  <parent></parent>
</div>

在上面的代码片段中,假设data.text是已清理的HTML. <子>是一些做有用的东西的子组件,我想要包装提前未知的data.text块. (这里的输入只是用于演示.这个MCVE并不像我正在构建的代码那样,它只是一个例子,显示了我坚持的那种情况.)

那么:我将如何更改输出函数或父组件的模板,以便输入的HTML和插入的< child>模板是否正确呈现?

我试过的

>在Vue 1中,答案是简单的$compile.我正在使用vuejs2删除了$compile(出于合理的担忧,它使得它很容易天真地引入XSS漏洞.)
> v-html清理你喂它的内容,它会删除子组件.显然这是not the way to do this.(该页面建议使用partials,但我不确定如何应用于这种情况;在任何情况下,部分也已从vue2中删除.)
>我已经尝试将output()的结果传递给另一个组件,然后将其用作模板.这似乎是一种很有前景的方法,但我无法弄清楚如何更改辅助组件的模板.模板只接受一个字符串,而不是像许多其他组件属性一样的函数,所以我不能传递模板html,比如说道具.像重写this.template在beforeMount()或bind()之类的东西本来不错,但也没有喜悦.是否有其他方法可以在安装之前替换组件的模板字符串?
>与模板不同,我可以将数据传递给组件的render()函数……但是我仍然不得不将该html字符串解析为嵌套的createElement函数.这正是070​​01;有没有什么方法可以解决这个问题,而不是自己重新发明它?

Vue.component('foo',{
  props: ['myInput'],render(createElement) {
    console.log(this.myInput); // this works...
    // ...but how to parse the html in this.myInput into a usable render function?
    // return createElement('div',this.myInput);
  },})

>我无法使用内联模板欺骗我的内容:< foo inline-template> {{$parent.output}}< / foo>与普通的{{output}}完全相同.回想起来应该是显而易见的,但值得一试.
>也许在运行中构建async component就是答案?这可以清楚地生成一个具有任意模板的组件,但是如何合理地从父组件中调用它,并将输出提供给构造函数? (它需要可以通过不同的输入重用,同时可能同时看到多个实例;没有全局或单例.)
>我甚至认为有些荒谬的东西,比如输出()将输入分成一个数组,在它插入的位置< child>,然后在主模板中做这样的事情:

...
  <template v-for="chunk in output">
      <span v-html="chunk"></span>
      <child>...</child>
  </template>
  ....

如果费力的话,这将是可行的 – 我必须将孩子的插槽中的内容拆分成一个单独的数组,并在v-for期间通过索引获取它,但这可以完成…如果输入是普通的文本而不是HTML.在拆分HTML时,我经常会在每个块中使用不平衡的标签,这会在v-html为我重新平衡时弄乱格式化.无论如何,这整个策略感觉就像一个糟糕的黑客;肯定有更好的办法.

>也许我只是将整个输入放入v-html然后(不知何故)通过事后DOM操作将子组件插入到适当的位置?我没有深入探讨这个选项,因为它也感觉像是一个黑客攻击,而且与数据驱动策略相反,但是如果所有其他方法都失败了,它可能是一种方法吗?

一些先发制人的免责声明

>我非常清楚$compile类操作中涉及的XSS风险.请放心,我所做的一切都不涉及未经授权的用户输入;用户不插入任意组件代码,而是组件需要在用户定义的位置插入子组件.
>我有理由相信这不是XY问题,我确实需要动态插入组件. (我希望从失败的尝试次数和盲目的小巷中可以看出我已经失败了,我已经在这个问题上加了一点思考!)那说,如果有不同的方法导致类似的结果,我’我的耳朵.重点是我知道我需要添加哪个组件,但我不能提前知道添加它的位置;该决定发生在运行时.
>如果它是相关的,在现实生活中我使用的是vue-cli webpack模板中的单文件组件结构,而不是上面示例中的Vue.component().不偏离该结构太远的答案是首选,但任何有效的方法都可行.

进展!

@BertEvans在评论中指出Vue.compile()是一个存在的东西,这是一个我不能相信 – 我错过了 – 如果有的话.

但是我仍然无法使用它而不诉诸于该文档中的全局变量.这会呈现,但硬编码全局模板:

var precompiled = Vue.compile('<span><child>test</child></span>');
Vue.component('test',{
  render: precompiled.render,staticRenderFns: precompiled.staticRenderFns
});

但是各种尝试将其重新转换为可以接受输入属性的东西都是不成功的(以下例如抛出“渲染函数中的错误:ReferenceError:_c未定义”,我假设因为staticRenderFns在渲染时还没准备好去他们需要吗?

Vue.component('test',{
  props: ['input'],render() { return Vue.compile(this.input).render()},staticRenderFns() {return Vue.compile(this.input).staticRenderFns()}
});

(这不是因为有两个单独的compile() – 在beforeMount()中执行预编译然后返回它的render和staticRenderFns会抛出相同的错误.)

这真的感觉它是在正确的轨道上,但我只是坚持一个愚蠢的语法错误或类似…

解决方法

正如我在上面的评论中所提到的,$compile已被删除,但Vue.compile在某些版本中可用.除了几个案例之外,使用以下内容可以起到我相信您的意图.
Vue.component('child',text: '<div><p>Lorem ipsum dolor sit amet.</p><p><i>Lorem ipsum!</i></p></div>'
    }
  },template: `<div>
      Search: <input type='text' v-model="input"><br>
      <hr>
      <div><component :is="output"></component></div>
    </div>`,computed: {
    output() {
      if (!this.input)
         return Vue.compile(this.text)
      /* This is the wrong approach; what do I replace it with? */
      var out = this.text;
      if (this.input) {
        this.input = this.input.replace(/[^a-zA-Z\s]/g,'<child><b>' + this.input + '</b></child>');
        out = Vue.compile(out)
      }
      return out;
    }
  }
});

new Vue({
  el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<div id="app">
  <parent></parent>
</div>

你提到你正在使用webpack构建,我相信该构建的认值是没有编译器的Vue,所以你需要修改它以使用不同的构建.

添加一个动态组件来接受编译输出的结果.

示例文本不是有效模板,因为它具有多个根.我添加一个包装div,使其成为一个有效的模板.

一个注意事项:如果搜索词与文本中的任何HTML标记的全部或部分匹配,则会失败.例如,如果输入“i”,或“di”或“p”,结果将不是您所期望的,某些组合将在编译时引发错误.

相关文章

vue阻止冒泡事件 阻止点击事件的执行 &lt;div @click=&a...
尝试过使用网友说的API接口获取 找到的都是失效了 暂时就使用...
后台我拿的数据是这样的格式: [ {id:1 , parentId: 0, name:...
JAVA下载文件防重复点击,防止多次下载请求,Cookie方式快速简...
Mip是什么意思以及作用有哪些