<textarea> 组件被 Vue 渲染器视为静态的,因此在将它们放入 DOM 后,它们根本不会改变(这就是为什么如果你检查 DOM,你会在你的内部看到 <slot></slot> <textarea>)。
但即使他们确实改变了,那也无济于事。仅仅因为<textarea>s 中的 HTML 元素没有成为它们的价值。您必须设置TextArea element 的value 属性才能使其工作。
无论如何,不要绝望。这是可行的,解决上述问题所需要做的就是使用一个小的帮助组件。
有很多可能的方法来实现这一点,下面显示了两种。它们的基本区别在于您希望原始组件的模板如何。
解决方法:将<textarea>改成<textarea-slot>组件
您的组件模板现在将变为:
<template>
<div>
<textarea-slot
v-model="myContent"
:name="name"
:id="id">
<slot></slot>
</textarea-slot>
</div>
</template>
如您所见,除了将 <textarea> 替换为 <textarea-slot> 外,没有任何变化。这足以克服 Vue 对<textarea> 的静态处理。 <textarea-slot> 的完整实现在下面的演示中。
替代方案:保留<textarea>,但通过<vnode-to-html>组件获取<slot>的HTML
解决方案是创建一个帮助组件(下面命名为 vnode-to-html),它将您的插槽的 VNodes 转换为 HTML 字符串。然后,您可以将这些 HTML 字符串设置为您的<textarea> 的value。您的组件模板现在将变为:
<template>
<div>
<vnode-to-html :vnode="$slots.default" @html="valForMyTextArea = $event" />
<textarea
:value="valForMyTextArea"
:name="name"
:id="id">
</textarea>
</div>
</template>
在这两种选择中...
my-component 的用法保持不变:
<my-component>
<p class="textbox">hello world</p>
</my-component>
完整的工作演示:
Vue.component('my-component', {
props: ["content", "name", "id"],
template: `
<div>
<textarea-slot
v-model="myContent"
:name="name"
:id="id">
<slot></slot>
</textarea-slot>
<vnode-to-html :vnode="$slots.default" @html="valueForMyTextArea = $event" />
<textarea
:value="valueForMyTextArea"
:name="name"
:id="id">
</textarea>
</div>
`,
data() { return {valueForMyTextArea: '', myContent: null} }
});
Vue.component('textarea-slot', {
props: ["value", "name", "id"],
render: function(createElement) {
return createElement("textarea",
{attrs: {id: this.$props.id, name: this.$props.name}, on: {...this.$listeners, input: (e) => this.$emit('input', e.target.value)}, domProps: {"value": this.$props.value}},
[createElement("template", {ref: "slotHtmlRef"}, this.$slots.default)]
);
},
data() { return {defaultSlotHtml: null} },
mounted() {
this.$emit('input', [...this.$refs.slotHtmlRef.childNodes].map(n => n.outerHTML).join('\n'))
}
});
Vue.component('vnode-to-html', {
props: ['vnode'],
render(createElement) {
return createElement("template", [this.vnode]);
},
mounted() {
this.$emit('html', [...this.$el.childNodes].map(n => n.outerHTML).join('\n'));
}
});
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<my-component>
<p class="textbox">hell
o world1</p>
<p class="textbox">hello world2</p>
</my-component>
</div>
细分:
- Vue 将
<slot>s 解析为VNodes 并使其在this.$slots.SLOTNAME 属性中可用。默认插槽自然是this.$slots.default。
- 因此,在运行时,您可以使用通过
<slot> 传递的内容(作为this.$slots.default 中的VNodes)。现在的挑战变成了如何将这些 VNode 转换为 HTML 字符串? 这是一个复杂的 still open, issue,将来可能会得到不同的解决方案,但即使有,it will most likely take a while .
- 上述两种解决方案(
template-slot 和 vnode-to-html)都使用 Vue 的渲染函数将 VNode 渲染到 DOM,然后获取渲染的 HTML。
- 由于提供的插槽可能具有任意 HTML,我们将 VNode 渲染为 HTML Template Element,它不会执行任何
<script> 标签。
- 这两种解决方案的区别在于它们如何“处理”从渲染函数生成的 HTML。
-
vnode-to-html 作为事件返回,父级 (my-component) 使用传递的值设置将设置为 textarea 的 :value 的 data 属性。
-
textarea-slot 将自己声明为 <textarea>,而父不必这样做。这是一种更简洁的解决方案,但需要更加小心,因为您必须指定要将哪些属性传递给在 textarea-slot 中创建的 <textarea>。
总结和现成的替代品
尽管有可能,重要的是要知道 Vue 在将声明的 <template> 解析为 <slot>s 时,会去除一些格式信息,例如顶级组件之间的空格。同样,它会去除<script> 标签(因为它们不安全)。这些是使用 <slot>s 的任何解决方案所固有的警告(此处是否显示)。所以要注意。
Vue 的典型富文本编辑器通过使用v-model(或value)属性将代码传递到组件中来完全解决这个问题。
众所周知的例子包括:
他们的网站(上面链接)都有很好的文档,所以我在这里重复它们几乎没有用处,但作为一个例子,看看 codemirror 如何使用 value 属性传递代码:
<codemirror ref="myCm"
:value="code"
:options="cmOptions"
@ready="onCmReady"
@focus="onCmFocus"
@input="onCmCodeChange">
</codemirror>
所以他们就是这样做的。当然,如果<slot>s - 及其注意事项 - 适合您的用例,它们也可以使用。