首页 > 解决方案 > Vue 组件槽不被视为子槽吗?

问题描述

假设我有一个父组件 Tabs,它将子 Tab 组件作为插槽。

所以设置是这样的:

选项卡(父)

<div>
  <slot name="tabChild">
</div>

选项卡(儿童)

<div>
  Tab {{name}}
</div>

我的应用

<Tabs>
   <Tab slot="tabChild" name="1" ></Tab>
   <Tab slot="tabChild" name="1" ></Tab>
</Tabs>

但是,在 Tabs (Parent) 组件中,当我尝试以编程方式访问其子组件时,如下所示:

选项卡组件

mounted(){
  let childTabs = this.$children  //this is empty?? 
  childTabs = this.$slots //this is correctly the child Tab Components
}

此外,在 Tab (Child) 组件中,当我尝试访问其父组件时,我认为它是 Tabs 组件(因为它们被插入其中),它不是:

选项卡组件

mounted(){
   let parentTab = this.$parent //this is MyApp (grandfather), NOT Tabs
}

为什么选项卡子组件插入更大的选项卡组件而不是其子组件?

标签: vue.jsvuejs2vue-component

解决方案


好吧,$parent将始终引用嵌套在特定组件内部的“直接”父/包装器,因此当需要引用“根”父时,它并不十分可靠。

以下是官方文档的一些很好的摘录:

  1. 在大多数情况下,深入到父级会使您的应用程序更难调试和理解,尤其是当您更改父级中的数据时。稍后查看该组件时,将很难弄清楚该突变来自何处。[1]

  2. 使用$parent$children谨慎,因为它们主要用作逃生舱口。更喜欢使用道具和事件进行父子通信。[2]

  3. 隐式父子通信:Props 和事件应该是父子组件通信的首选,而不是this.$parent或 mutating props。
    一个理想的 Vue 应用程序是props down,events up。坚持这个约定会让你的组件更容易理解。[3]

  4. 不幸的是,使用该$parent属性并不能很好地扩展到嵌套更深的组件。这就是依赖注入可能有用的地方,使用两个新的实例选项:provideinject. [4]


这是一个演示上述建议的快速示例:

const Parent = Vue.extend({
  template: `
    <ul :style="{ columnCount: colCount }">
      <slot></slot>
    </ul>
  `,

  provide() {
    return {
      biologicalParent: this
    }
  },

  props: ['value', 'colCount', 'message'],
  
  methods: {
    sayIt() {
      const 
        num = this.$children.indexOf(this.value) + 1,
        
        message = [
          `I am child no. ${num}`,
          `among ${this.$children.length} of my siblings`,
          `in ${this.colCount} different lineages.`
        ]
        .join(' ');

      this.$emit('update:message', message);
    }
  },
  
  watch: {
    value: 'sayIt'
  }
});

const Child = Vue.extend({
  template: `<li v-text="name" @click="setChild"></li>`,

  props: ['name'],

  inject: ['biologicalParent'],

  methods: {
    setChild() {
      this.biologicalParent.$emit('input', this);
    }
  }
});

new Vue({
  el: '#demo',

  data: () => ({
    lineage: 3,
    childCount: 10,
    message: 'Click on a child for the associated message.',
    lastChild: undefined
  }),

  components: {
    Parent,
    Child
  }
});
input {
  width: 4em;
}

li {
  cursor: pointer;
}

.message {
  background-color: beige;
  border: 1px solid orange;
  padding: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="demo">
  <table>
    <tr>
      <td>Child count:</td>
      <td><input type="number" v-model="childCount" /></td>
    </tr>

    <tr>
      <td>Lineage:</td>
      <td><input type="number" v-model="lineage" /></td>
    </tr>
  </table>

  <parent 
    v-model="lastChild" 
    :col-count="lineage"
    :message.sync="message">
    <child 
      v-for="(n, i) of Array.apply(null, {length: childCount})" 
      :key="i" 
      :name="`child #${i+1}`">
    </child>
  </parent>

  <p class="message">{{ message }}</p>
</div>

这些provide选项允许我们指定datamethods我们想要提供给后代组件。在上面的例子中,这就是Parent实例。
请注意如何Child在运行时(单击时)而不是编译时(渲染阶段)评估索引/编号。


推荐阅读