首页 > 解决方案 > 如何解决 - 在同一渲染树中发现重复的插槽“默认”存在

问题描述

<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 的限制,那么请告诉我如何克隆插槽(或类似的东西)并让它仍然是响应式的。

标签: javascriptvue.jsvuejs2vue-component

解决方案


使用渲染函数应该能够实现你需要的东西,就像下面的演示:

但是你可能会遇到一些麻烦,比如这个链接描述的:为什么重复的插槽不好?.

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

在 DOM 元素的实际创建过程中,Vue 会多次重复使用相同的 vnode 对象(代表元素)。

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

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

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

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

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

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>


推荐阅读