如何将HTML模板作为属性传递给Vue组件

6

我有一个包含html标签textarea组件,我想在此组件中以编辑模式获取html代码。我使用Laravel生成html。

<template>
    <div>
        <textarea
                  :value="content"
                  :name="name"
                  :id="id">
              <slot></slot>
        </textarea>
    </div>
</template>

在Blade页面中,我使用了这个组件:
<my-component>
   <p class="textbox">hello world</p>
</my-component>

当我把这个组件放到页面上时,在文本框中显示了标签<slot></slot>。我该怎么办?你有任何解决方法吗?
谢谢。

1
请问您能否详细解释一下您想要做什么?您是想从父组件中给<textarea>赋值吗? <slot>似乎不起作用,但:value可以正常工作,并且您似乎已经在使用它了。 - Rashad Saleh
看起来你采用了错误的解决方案来获取某些功能(插槽不能在文本区域内使用)。正如@rashad所建议的,我们需要更详细的描述你想要实现什么,才能提出适当的解决方案。 - Jacek J
我之前的评论中有一个错别字。应该是“你是想要给...”而不是“你是想要五...”。 - Rashad Saleh
3个回答

12

<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-slotvnode-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> - 带有其注意事项 - 适合您的用例,它们也可以使用。


5
短答案是不可能
你的插槽(slot)被放在一个文本域(textarea)标记内。文本域标记只能显示其框中的文本内容。
因此,如果您想要一种“HTML编辑模式”,您可能正在寻找所谓的所见即所得(WYSIWYG)编辑器,我推荐您可以使用CKEditor for VueJS,该编辑器甚至允许您直接编辑HTML代码。 https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/frameworks/vuejs.html 你的HTML。
<div id="app">
    <ckeditor :editor="editor" v-model="editorData" :config="editorConfig"></ckeditor>
</div>

您的组件
const app = new Vue( {
    el: '#app',
    data: {
        editor: ClassicEditor,
        editorData: '<p>Editable Content HTML</p>',
        editorConfig: {
            // The configuration of the editor.
        }
    }
} );

0
在您的情况下,如果您想编写自己的内容编辑器,可以使用带有属性contenteditable="true"的
而不是

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接