【问题标题】:How to pass html template as props to Vue component如何将 html 模板作为道具传递给 Vue 组件
【发布时间】:2019-10-23 16:09:01
【问题描述】:

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

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

在刀片页面我习惯了这个组件:

<my-component>
   <p class="textbox">hello world</p>
</my-component>

当我将此组件放入页面时,请在 textarea 中显示标签 &lt;slot&gt;&lt;/slot&gt;。我该怎么办?您对我的需求有什么解决方案吗?

谢谢

【问题讨论】:

  • 你能解释一下你想要做什么吗?您是否尝试将 &lt;textarea&gt; 的值设为其父级的值? &lt;slot&gt; 似乎不起作用,但 :value 确实起作用,而且您似乎已经在使用它了。
  • 看起来你采取了错误的解决方案来获得某些功能(插槽不能在 textarea 中使用)。正如@rashad 所建议的那样,我们需要更详细地描述您想要实现的目标,以便能够提出适当的解决方案。
  • 我之前的评论中有一个错字。而不是“您是否尝试五个 ...”应该是“您是否尝试给予 ...”。

标签: javascript html vue.js


【解决方案1】:

&lt;textarea&gt; 组件被 Vue 渲染器视为静态的,因此在将它们放入 DOM 后,它们根本不会改变(这就是为什么如果你检查 DOM,你会在你的内部看到 &lt;slot&gt;&lt;/slot&gt; &lt;textarea&gt;)。

但即使他们确实改变了,那也无济于事。仅仅因为&lt;textarea&gt;s 中的 HTML 元素没有成为它们的价值。您必须设置TextArea elementvalue 属性才能使其工作。

无论如何,不​​要绝望。这是可行的,解决上述问题所需要做的就是使用一个小的帮助组件。

有很多可能的方法来实现这一点,下面显示了两种。它们的基本区别在于您希望原始组件的模板如何。

解决方法:将&lt;textarea&gt;改成&lt;textarea-slot&gt;组件

您的组件模板现在将变为:

<template>
    <div>
      <textarea-slot
                v-model="myContent"
                :name="name"
                :id="id">
            <slot></slot>
      </textarea-slot>
    </div>
</template>

如您所见,除了将 &lt;textarea&gt; 替换为 &lt;textarea-slot&gt; 外,没有任何变化。这足以克服 Vue 对&lt;textarea&gt; 的静态处理。 &lt;textarea-slot&gt; 的完整实现在下面的演示中。

替代方案:保留&lt;textarea&gt;,但通过&lt;vnode-to-html&gt;组件获取&lt;slot&gt;的HTML

解决方案是创建一个帮助组件(下面命名为 vnode-to-html),它将您的插槽的 VNodes 转换为 HTML 字符串。然后,您可以将这些 HTML 字符串设置为您的&lt;textarea&gt;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 将&lt;slot&gt;s 解析为VNodes 并使其在this.$slots.SLOTNAME 属性中可用。默认插槽自然是this.$slots.default
  • 因此,在运行时,您可以使用通过&lt;slot&gt; 传递的内容(作为this.$slots.default 中的VNodes)。现在的挑战变成了如何将这些 VNode 转换为 HTML 字符串? 这是一个复杂的 still open, issue,将来可能会得到不同的解决方案,但即使有,it will most likely take a while .
  • 上述两种解决方案(template-slotvnode-to-html)都使用 Vue 的渲染函数将 VNode 渲染到 DOM,然后获取渲染的 HTML。
  • 由于提供的插槽可能具有任意 HTML,我们将 VNode 渲染为 HTML Template Element,它不会执行任何 &lt;script&gt; 标签。
  • 这两种解决方案的区别在于它们如何“处理”从渲染函数生成的 HTML。
    • vnode-to-html 作为事件返回,父级 (my-component) 使用传递的值设置将设置为 textarea:valuedata 属性。
    • textarea-slot 将自己声明为 &lt;textarea&gt;,而父不必这样做。这是一种更简洁的解决方案,但需要更加小心,因为您必须指定要将哪些属性传递给在 textarea-slot 中创建的 &lt;textarea&gt;


总结和现成的替代品

尽管有可能,重要的是要知道 Vue 在将声明的 &lt;template&gt; 解析为 &lt;slot&gt;s 时,会去除一些格式信息,例如顶级组件之间的空格。同样,它会去除&lt;script&gt; 标签(因为它们不安全)。这些是使用 &lt;slot&gt;s 的任何解决方案所固有的警告(此处是否显示)。所以要注意。

Vue 的典型富文本编辑器通过使用v-model(或value)属性将代码传递到组件中来完全解决这个问题。

众所周知的例子包括:

他们的网站(上面链接)都有很好的文档,所以我在这里重复它们几乎没有用处,但作为一个例子,看看 codemirror 如何使用 value 属性传递代码:

<codemirror ref="myCm"
            :value="code" 
            :options="cmOptions"
            @ready="onCmReady"
            @focus="onCmFocus"
            @input="onCmCodeChange">
</codemirror>

所以他们就是这样做的。当然,如果&lt;slot&gt;s - 及其注意事项 - 适合您的用例,它们也可以使用。

【讨论】:

    【解决方案2】:

    简短的回答是不可能

    您的 slot 放在 textarea 标记内。 Textare 标签只能在其 box 上显示文本内容。

    所以如果你想要一种“HTML 编辑模式”,你可以寻找一个所见即所得的编辑器,我建议你可以使用 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.
            }
        }
    } );
    

    【讨论】:

      【解决方案3】:

      如果您想编写自己的内容编辑器,可以使用 div 属性 contenteditable="true" 而不是 textarea。在此之后,您可以编写文本装饰方法... 生成的带有 laravel 的 html 存储在 myhtml 并在 vue 组件中使用。

      例子:我也上传到codesandbox [Simple Vue Editor]

      <template>
        <div>
          <button @click="getEditorCotent">Get Content</button>
          <button @click="setBold">Bold</button>
          <button @click="setItalic">Italic</button>
          <button @click="setUnderline">Underline</button>
          <button @click="setContent">Clear</button>
          <div class="myeditor" ref="myeditor" contenteditable v-html="myhtml" @input="onInput"></div>
        </div>
      </template>
      
      <script>
      export default {
        name: "HelloWorld",
        props: {
          msg: String
        },
        data: () => {
          return {
            myhtml:
              "<h1>Simple editor</h1><p  style='color:red'>in vue</p><p>Hello world</p>" // from laravel server via axios call
          };
        },
        methods: {
          onInput(e) {
            // handle user input
            // e.target.innerHTML
          },
          getEditorCotent() {
            console.log(this.$refs.myeditor.innerHTML);
          },
          setBold() {
            document.execCommand("bold");
          },
          setItalic() {
            document.execCommand("italic");
          },
          setUnderline() {
            document.execCommand("underline");
          },
          setContent() {
            // that way set your html content
            this.myhtml = "<b>You cleared the editor content</b>";
          }
          // PS. Good luck!
        }
      };
      </script>
      
      <style scoped>
      .myeditor {
        /* text-align: left; */
        border: 2px solid gray;
        padding: 5px;
        margin: 20px;
        width: 100%;
        min-height: 50px;
      }
      </style>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-01-08
        • 2018-08-01
        • 1970-01-01
        • 2018-09-23
        • 2019-12-27
        • 2021-12-02
        • 2019-08-20
        • 2021-07-30
        相关资源
        最近更新 更多