我想在不一定是预定义的 HTML 块中的任意点动态插入新的 vuejs 组件。
这是一个稍微做作的示例,演示了我正在尝试做的事情:
Vue.component('child', {
// pretend I do something useful
template: '<span>--><slot></slot><--</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。<child>
是一些有用的子组件,我想将其包裹起来data.text
这是事先不知道的。 (input
这里只是为了演示。这个 MCVE 并不真正类似于我正在构建的代码,它只是一个示例,显示了我所遇到的情况。)
那么:我将如何改变output
函数或父组件的模板,这样 HTML 就可以来自input
和插入的<child>
模板是否正确呈现?
我尝试过的
-
在 Vue 1 中,这个问题的答案很简单$compile
。我正在使用 vuejs2 删除了$compile
(出于合理的担忧,它使得很容易天真地引入 XSS 漏洞。)
-
v-html 会清理您提供的内容,从而将子组件剥离出来。显然这是不是这样做的方法。 (该页面建议使用部分,但我不确定如何将其应用于这种情况;无论如何,部分也已从 vue2 中删除。)
-
我尝试过传递结果output()
到另一个组件中,然后该组件将使用它作为模板。这似乎是一种很有前途的方法,但我不知道如何更改辅助组件的模板。template
只接受字符串,而不是像许多其他组件属性那样的函数,因此我无法将模板 html 传递到 prop 中。类似于重写的东西this.template
inside beforeMount()
or bind()
本来很好,但也没有什么快乐。在安装组件之前是否有其他方法可以替换组件的模板字符串?
-
Unlike template
, I can将数据传递给组件render()
函数...但是我仍然必须将 html 字符串解析为嵌套的createElement
功能。到底是什么Vue 首先是在内部做的;除了我自己重新发明之外,有什么方法可以解决这个问题吗?
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}}
。回想起来,这应该是显而易见的,但值得一试。
-
也许构建一个异步组件答案是在飞行中?这显然可以生成具有任意模板的组件,但我如何合理地从父组件调用它,并提供output
到构造函数? (它需要可通过不同的输入重用,多个实例可能同时可见;没有全局变量或单例。)
-
我什至考虑过一些荒谬的事情,比如output()
在要插入的点将输入拆分为数组<child>
,然后在主模板中执行类似的操作:
...
<template v-for="chunk in output">
<span v-html="chunk"></span>
<child>...</child>
</template>
....
这是可行的,如果费力的话——我也必须将子插槽中的内容拆分到一个单独的数组中,并在 v-for 期间通过索引获取它,但这可以做到......if input
是纯文本而不是 HTML。在分割 HTML 时,我经常会遇到每个标签不平衡的情况chunk
,这可能会弄乱格式v-html
为我重新平衡它。无论如何,这整个策略感觉像是一个糟糕的黑客;一定会有更好的办法。
- 也许我只是将整个输入放入
v-html
然后(以某种方式)通过事后 DOM 操作将子组件插入到正确的位置?我还没有太深入地探索这个选项,因为它也感觉像是一种黑客攻击,与数据驱动策略相反,但如果其他一切都失败了,也许这是一种可行的方法?
一些先发制人的免责声明
- 我非常清楚涉及的 XSS 风险
$compile
类似的操作。请放心,我所做的一切都不会以任何方式涉及未经处理的用户输入;用户不是插入任意组件代码,而是组件需要在用户定义的位置插入子组件。
- 我有理由相信这不是 XY 问题,我确实需要动态插入组件。 (我希望从我所经历的失败尝试和死胡同的数量中可以明显看出,我在这方面投入了更多的思考!)也就是说,如果有一种不同的方法会导致类似的结果,我'我洗耳恭听。重点是我知道which我需要添加组件,但我无法提前知道where添加它;该决定发生在运行时。
- 如果相关的话,在现实生活中我使用的是 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
还没准备好出发render
需要他们吗?
Vue.component('test', {
props: ['input'],
render() { return Vue.compile(this.input).render()},
staticRenderFns() {return Vue.compile(this.input).staticRenderFns()}
});
(这并不是因为有两个独立的compile()
s -- 在内部进行预编译beforeMount()
然后返回其渲染,而 staticRenderFns 会引发相同的错误。)
这确实感觉它走在正确的轨道上,但我只是陷入了一个愚蠢的语法错误或类似的错误......