且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何将HTML模板作为道具传递给Vue组件

更新时间:2021-09-20 04:27:02

<textarea>组件被Vue渲染器视为静态组件,因此将它们放入DOM后,它们根本不会发生变化(因此,这就是为什么如果您检查DOM,则会在<textarea>的内部看到<slot></slot>.

<textarea> components are treated as static by the Vue renderer, thus after they are put into the DOM, they don't change at all (so that's why if you inspect the DOM you'll see <slot></slot> inside your <textarea>).

但是即使改变了,也无济于事.只是因为<textarea>中的HTML元素没有成为它们的值.您必须设置 TextArea元素的value属性使它正常工作.

But even it if they did change, that wouldn't help much. Just because HTML elements inside <textarea>s don't become their value. You have to set the value property of the TextArea element to make it work.

无论如何,请不要绝望.这是可行的,您需要克服的上述问题就是发挥一个小的辅助组件的作用.

Anyway, don't despair. It is doable, all you need to overcome the issues above is to bring a small helper component into play.

有很多方法可以实现这一目标,如下所示.它们在本质上有不同,您希望原始组件的模板是这样.

There are many possible ways to achieve this, two shown below. They differ basically in how you would want your original component's template to be.

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

Your component's template would now become:

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

如您所见,除了用<textarea-slot>替换<textarea>外,什么都没有改变.这足以克服Vue对<textarea>进行的静态处理. <textarea-slot>的完整实现在下面的演示中.

As you can see, nothing but replacing <textarea> with <textarea-slot> changed. This is enough to overcome the static treatment Vue gives to <textarea>. The full implementation of <textarea-slot> is in the demo below.

解决方案是创建一个助手组件(在下面命名为vnode-to-html),该组件将转换插槽的

The solution is to create a helper component (named vnode-to-html below) that would convert your slot's VNodes into HTML strings. You could then set such HTML strings as the value of your <textarea>. Your component's template would now become:

<template>
    <div>
        <vnode-to-html :vnode="$slots.default" @html="valForMyTextArea = $event" />
        <textarea
                  :value="valForMyTextArea"
                  :name="name"
                  :id="id">
        </textarea>
    </div>
</template>

在这两种选择中...

my-component的用法保持不变:

In both alternatives...

The usage of the my-component stays the same:

<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 parses the <slot>s into VNodes and makes them available in the this.$slots.SLOTNAME property. The default slot, naturally, goes in this.$slots.default.
  • So, in runtime, you have available to you what has been passed via <slot> (as VNodes in this.$slots.default). The challenge now becomes how to convert those VNodes to HTML String? This is a complicated, still open, issue, which may get a different solution in the future, but, even if it ever does, it will most likely take a while.
  • Both solutions above (template-slot and vnode-to-html) use Vue's render function to render the VNodes to the DOM, then picks up the rendered HTML.
  • Since the supplied slots may have arbitrary HTML, we render the VNodes into an HTML Template Element, which doesn't execute any <script> tags.
  • The difference between the two solutions is just how they "handle back" the HTML generated from the render function.
    • The vnode-to-html returns as an event that should be picked up by the parent (my-component) which uses the passed value to set a data property that will be set as :value of the textarea.
    • The textarea-slot declares itself a <textarea>, to the parent doesn't have to. It is a cleaner solution, but requires more care because you have to specify which properties you want to pass down to the <textarea> created inside textarea-slot.


    但是,重要的是要知道Vue在将声明的<template>解析为<slot>时,会剥离一些格式信息,例如***组件之间的空格.同样,它会剥离<script>标记(因为它们不安全).这些是使用<slot>的任何解决方案所固有的警告(此处未显示).所以要注意.

    However possible, it is important to know that Vue, when parsing the declared <template> into <slot>s, will strip some formatting information, like whitespaces between top-level components. Similarly, it strips <script> tags (because they are unsafe). These are caveats inherent to any solutions using <slot>s (presented here or not). So be aware.

    Vue的典型富文本编辑器通过使用v-model(或value)属性将代码传递到组件中,从而完全解决了此问题.

    Typical rich text editors for Vue, work around this problem altogether by using v-model (or value) attributes to pass the code into the components.

    众所周知的例子包括:

    • vue-ace-editor: Demo/codepen here.
    • Vue Prism Editor: Demo here.
    • vue-monaco (the code editor that powers VS Code): demo here.
    • vue-codemirror: Demo here. This is by far the most starred on github.

    他们在他们的网站上都有非常好的文档(如上链接),所以在这里重复它们对我没有多大用处,但仅作为示例,请参见codemirror如何使用value道具来传递代码:

    They all have very good documentation in their websites (linked above), so it would be of little use for me to repeat them here, but just as an example, see how codemirror uses the value prop to pass the code:

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

    所以他们就是这样做的.当然,如果<slot> s(带有警告)适合您的用例,则也可以使用它们.

    So that's how they do it. Of course, if <slot>s - with its caveats - fit your use case, they can be used as well.