【问题标题】:how to resolve - Duplicate presence of slot "default" found in the same render tree如何解决 - 在同一渲染树中发现重复的插槽“默认”存在
【发布时间】:2018-10-18 14:47:16
【问题描述】:

多次使用<slot> 时出现以下错误:

在同一渲染树中发现重复的插槽“默认”存在 - 这可能会导致渲染错误。

有些解决方案使用“作用域插槽”,但我的理解是使用带有v-for 的解决方案很好。我不确定,我可能错了,如果我错了,请告诉我。

我有一种情况,我需要在子组件中多次复制静态内容(带有标记)。

父组件:

<template>
  <child-comp>
    <h1>Lorem Ipusm</h1>
    <button @click="fnDoSomething">Yahoo!<button>
    <!-- ... there will be a lot more that will go here in the default slot -->
  <child-comp>     
<template>

子组件:

<template>
  <div>
    <h2>Need one default slot here</h2>
    <slot><slot>
    <div>
      <h2>Need one more default slot here</h2>
      <slot><slot>
    <div>
  </div>      
<template>

如果上述问题无法修复或者它是 Vue.js 的限制,那么请告诉我如何克隆插槽(或类似的东西)并让它仍然是响应式的。

【问题讨论】:

    标签: javascript vue.js vuejs2 vue-component


    【解决方案1】:

    使用render function 应该可以实现你所需要的,就像下面的演示:

    但是你可能会遇到一些麻烦,比如这个链接描述:Why are duplicated slots bad?

    正如 Vue.js 核心团队的开发者所说:

    Vue 将重用相同的 vnode 对象(代表元素) 在实际创建 DOM 元素期间多次。

    这样做的问题是每个 vnode 都获得了对其的引用 对应的DOM元素集。

    如果您多次重复使用相同的 vnode 对象,这些引用 被覆盖,你最终得到的 DOM 元素没有 虚拟 dom 中的表示,或引用错误的 vnode 元素。

    所以在下面的demo中,当你点击按钮时,你会发现第一个case的第一个slot没有同步(VNode被覆盖)。

    如果您的默认插槽是完全静态的内容,然后没有绑定到任何属性和方法,那么在 render() 中使用多个默认插槽应该是可以的。但如果没有,您必须使用作用域插槽来实现您所需要的。

    或者您可以深度克隆 this.$slots.default(检查VNode constructor),它会避免覆盖问题。 (查看下面演示中的第三种情况)

    Vue.config.productionTip = false
    Vue.component('child', {
      render: function (createElement) {
        return createElement(
          'div',
          [
          this.$slots.default, // default slot
            createElement('div', {
              attrs: {
                name: 'test'
              },
              style: {fontSize: '10px', 'color':'green'}
            }, this.$slots.default) // default slot
          ]
        )
      }
    })
    
    function deepClone(vnodes, createElement){
     let clonedProperties = ['text','isComment','componentOptions','elm','context','ns','isStatic','key']
     function cloneVNode(vnode) {
    	 let clonedChildren = vnode.children && vnode.children.map(cloneVNode)
    	 let cloned = createElement(vnode.tag, vnode.data, clonedChildren)
       clonedProperties.forEach(function(item){
        cloned[item] = vnode[item]
       })
    	 return cloned
     }
     return vnodes.map( cloneVNode )
    }
    
    Vue.component('child2', {
      render: function (createElement) {
        return createElement(
          'div',
          [
          this.$slots.default, // default slot
            createElement('div', {
              attrs: {
                name: 'test'
              },
              style: {fontSize: '10px', 'color':'green'}
            }, deepClone(this.$slots.default, createElement) ) // default slot
          ]
        )
      }
    })
    
    Vue.component('child1', {
      render: function (createElement) {
    
        return createElement(
          'div',
          [
          this.$slots.default, // default slot
            createElement('div', {
              attrs: {
                name: 'test'
              },
              style: {fontSize: '10px', 'color':'green'}
            }, this.$slots.my) // default slot
          ]
        )
      }
    })
    
    new Vue({
      el: '#app',
      data() {
        return {
          test: {
            'item': 'test',
            'prop1': 'a'
          }
        }
      },
      methods:{
        changeData: function() {
          this.test.item='none'
        }
      }
    })
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
    <div id="app">
    <button @click="changeData()">Click me!!!</button>
    <h1 style="background-color:red">Use multiple default slot:</h1>
    <child><h1>{{test}}</h1></child>
    <h1 style="background-color:red">Use scoped slot instead:</h1>
    <child1><h1>{{test}}</h1><template slot="my"><h1>{{test}}</h1></template></child1>
    <h1 style="background-color:red">Use Deep Clone (Default) instead:</h1>
    <child2><h1>{{test}}</h1><template slot="my"><h1>{{test}}</h1></template></child2>
    </div>

    【讨论】:

    • 感谢您的帮助。但是由于您提到的限制,我的问题仍然没有解决方案。我不确定是否将您的回答作为“已接受的答案”接受,但我当然赞成,再次感谢。
    • @Syed,深度克隆this.$slots.default 应该满足您的要求,请查看更新后的答案。
    • 我只是做了我的标记的一个组件,并将它放置在两个命名的插槽中两次,并确实使用了你的代码,因为你的代码对我来说有点难以理解,因为基本上我是一个几乎没有编程知识的设计师 :)
    猜你喜欢
    • 2019-10-06
    • 2018-12-06
    • 1970-01-01
    • 2020-11-15
    • 1970-01-01
    • 2021-04-16
    • 2019-03-07
    • 1970-01-01
    • 2013-11-28
    相关资源
    最近更新 更多