<textarea>
组件在Vue渲染器中被视为静态组件,因此它们被放入DOM后就不会发生任何变化(这就是为什么如果您检查DOM,您会看到<slot></slot>
在您的<textarea>
内部)。
但即使它们发生了变化,那也没什么用。因为<textarea>
内部的HTML元素不会成为其值。您必须设置TextArea元素的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>
解析为VNode
,并在this.$slots.SLOTNAME
属性中使它们可用。默认插槽自然放在this.$slots.default
中。
- 因此,在运行时,您可以使用通过
<slot>
传递的内容(作为this.$slots.default
中的VNodes)。现在面临的挑战是如何将这些VNodes转换为HTML字符串?这是一个复杂的仍然开放的问题,可能会在未来得到不同的解决方案,但即使它真的出现了,也很可能需要一段时间。
- 上述两种解决方案(
template-slot
和vnode-to-html
)都使用Vue的渲染函数将VNodes呈现到DOM中,然后获取呈现的HTML。
- 由于提供的插槽可能具有任意HTML,因此我们将VNodes呈现为HTML模板元素,该元素不执行任何
<script>
标记。
- 两种解决方案之间的区别仅在于它们如何“处理回”从渲染函数生成的HTML。
vnode-to-html
返回一个事件,应由父组件(my-component
)接收,该事件使用传递的值设置一个将作为:value
设置的data
属性的值。
textarea-slot
声明自己是一个<textarea>
,因此父组件无需声明。这是一种更干净的解决方案,但需要更多的注意,因为您必须指定要传递到textarea-slot
内部创建的<textarea>
的哪些属性。
总结和现成的替代方案
无论如何,重要的是要知道,Vue在将声明的<template>
解析为<slot>
时,会删除一些格式信息,比如顶层组件之间的空格。同样,它也会删除<script>
标签(因为它们是不安全的)。这些都是使用<slot>
的任何解决方案固有的注意事项(无论是在此处介绍的还是其他地方)。所以请注意。
Vue的典型富文本编辑器通过使用v-model
(或value
)属性将代码传递到组件中来完全解决了这个问题。
著名的例子包括:
它们的网站(以上链接)都有非常好的文档,因此我在这里重复它们将没有太大用处,但仅以codemirror为例,看看它如何使用value
属性传递代码:
<codemirror ref="myCm"
:value="code"
:options="cmOptions"
@ready="onCmReady"
@focus="onCmFocus"
@input="onCmCodeChange">
</codemirror>
这就是它们的实现方式。当然,如果<slot>
- 带有其注意事项 - 适合您的用例,它们也可以使用。
<textarea>
赋值吗?<slot>
似乎不起作用,但:value
可以正常工作,并且您似乎已经在使用它了。 - Rashad Saleh