【问题标题】:How to access $children in Vue 3 for creating a Tabs component?如何在 Vue 3 中访问 $children 以创建选项卡组件?
【发布时间】:2021-04-09 16:20:43
【问题描述】:

我正在尝试在 Vue 3 中创建一个类似于 question hereTabs 组件。

<tabs>
   <tab title="one">content</tab>
   <tab title="two" v-if="show">content</tab> <!-- this fails -->
   <tab :title="t" v-for="t in ['three', 'four']">{{t}}</tab> <!-- also fails -->
   <tab title="five">content</tab>
</tabs>

不幸的是,当内部的Tabs 是动态的,即如果Tab 上有一个v-if 或者当Tabs 使用v-for 循环呈现时,建议的解决方案不起作用 - 它失败。

我在这里为它创建了一个 Codesandbox,因为它包含 .vue 文件:

https://codesandbox.io/s/sleepy-mountain-wg0bi?file=%2Fsrc%2FApp.vue

我试过像onBeforeMount 一样使用onBeforeUpdate,但这也不起作用。实际上,它确实插入了新标签,但 标签的顺序发生了变化。

最大的障碍似乎是在 Vue 3 中似乎无法从父级获取/设置 child 数据。(如 Vue 2.x 中的 $children)。有人建议使用this.$.subtree.children,但随后强烈建议不要使用(无论如何我尝试过并没有帮助我)。

谁能告诉我如何使Tabs 中的Tab 反应并更新v-if 等?

【问题讨论】:

    标签: javascript vue.js vue-component vuejs3


    【解决方案1】:

    这看起来像是将项目索引用作v-for 循环的key 的问题。

    第一个问题是您将v-forkey 应用到了应该在父元素上的子元素(在本例中为&lt;li&gt;)。

    <li v-for="(tab, i) in tabs">
      <a :key="i"> ❌
      </a>
    </li>
    

    此外,如果 v-for 后备数组可以重新排列其项目(或删除中间项目),请不要将项目索引用作 key,因为该索引不会提供始终如一的唯一值。例如,如果从列表中删除了 3 项中的第 2 项,则第三项将向上移动到索引 1,采用之前被删除项使用的 key。由于列表中没有keys 发生变化,Vue 重用现有的虚拟 DOM 节点作为优化,不会发生重新渲染。

    在您的情况下选择一个好的key 是选项卡的title 值,因为在您的示例中每个选项卡始终是唯一的。这是您的新 Tab.vue,将 index 替换为 title 属性:

    // Tab.vue
    export default {
      props: ["title"], ?
      setup(props) {
        const isActive = ref(false)
        const tabs = inject("TabsProvider")
    
        watch(
          () => tabs.selectedIndex,
          () => {
            isActive.value = props.title === tabs.selectedIndex
          }                        ?
        )
    
        onBeforeMount(() => {
          isActive.value = props.title === tabs.selectedIndex
        })                       ?
    
        return { isActive }
      },
    }
    

    然后,更新您的Tabs.vue 模板以使用标签的title 而不是i

    <li class="nav-item" v-for="tab in tabs" :key="tab.props.title">
      <a                                                     ?
        @click.prevent="selectedIndex = tab.props.title"
        class="nav-link"                          ?
        :class="tab.props.title === selectedIndex && 'active'"
        href="#"          ?
      >
        {{ tab.props.title }}
      </a>
    </li>
    

    demo

    【讨论】:

    • 感谢您的帮助。我现在更好地理解了键控。但是在您的演示中,它仍然没有呈现 v-for 选项卡:请参阅 App.vue 中的 &lt;tab :title="t.title" v-for="t in more" :key="t"&gt;Dynamic {{ t }}&lt;/tab&gt;。有什么想法吗?
    • 还有一条评论。在 Vue 2 中,不需要使用 title 或其他任何内容“键入”选项卡,因为 $children 提供了对插槽子项的直接访问。所以&lt;Tabs&gt;&lt;Tab&gt;1&lt;/Tab&gt;&lt;Tab&gt;2&lt;/Tab&gt;&lt;/Tabs&gt; 完全没问题。现在看来我们需要在每个&lt;Tab&gt; 上指定一个title 或某种键。你能想出一种方法来识别 selectedTab 而不需要 tab.title 或任何其他键吗?
    • @supersan 我解决了有关缺少动态选项卡的问题(请参阅更新的演示),但这个解决方案(以及您链接到的原始解决方案)不是理想的 IMO,因为它需要进入 VNode 内部解析选项卡的标题只是为了显示选项卡标题。我认为更好的解决方案是将数据作为 Tabs 上的道具传递和/或使用作用域插槽。
    • 另外,考虑使用现有的选项卡组件,或提供该组件的库(例如,Vuetify、BootstrapVue、Buefy 等)。
    • 再次感谢您的帮助。你是对的,这个 Vnode 的东西很烂。在 Vue 2.x 中事情要简单得多,因为我们可以直接访问插槽的 $children,而且它也是反应式的,所以即使标签被拖动或排序,它也会自动更新。我确实研究了 Bootstrapvue、Vuetify 等,但那些还没有迁移到 Vue 3(谁知道他们面临同样的问题哈哈)。无论如何,所以我现在坚持使用 Vue 2.x。无论如何,这对我的需求来说已经绰绰有余了。我只是因为vite 而想升级,这看起来可以节省大量时间。
    【解决方案2】:

    这个解决方案是 Vuejs 论坛中的posted by @anteriovieira,看起来是正确的方法。缺少的一块拼图是getCurrentInstancesetup 期间可用

    完整的工作代码可以在这里找到:

    https://codesandbox.io/s/vue-3-tabs-ob1it

    我将其添加到此处,以供从 Google 来这里寻找相同内容的任何人参考。

    【讨论】:

      【解决方案3】:

      由于模板中的$slots 可以访问插槽(请参阅Vue documentation),因此您还可以执行以下操作:

      // Tabs component
      
      <template>
        <div v-if="$slots && $slots.default && $slots.default()[0]" class="tabs-container">
          <button
            v-for="(tab, index) in getTabs($slots.default()[0].children)"
            :key="index"
            :class="{ active: modelValue === index }"
            @click="$emit('update:model-value', index)"
          >
            <span>
              {{ tab.props.title }}
            </span>
          </button>
        </div>
        <slot></slot>
      </template>
      
      <script setup>
        defineProps({ modelValue: Number })
      
        defineEmits(['update:model-value'])
      
        const getTabs = tabs => {
          if (Array.isArray(tabs)) {
            return tabs.filter(tab => tab.type.name === 'Tab')
          } else {
            return []
          }
      </script>
      
      <style>
      ...
      </style>
      

      Tab 组件可能类似于:

      // Tab component
      
      <template>
        <div v-show="active">
          <slot></slot>
        </div>
      </template>
      
      <script>
        export default { name: 'Tab' }
      </script>
      
      <script setup>
        defineProps({
          active: Boolean,
          title: String
        })
      </script>
      

      实现应该类似于以下内容(考虑一个对象数组,每个部分一个,带有titlecomponent):

      ...
      <tabs v-model="active">
        <tab
          v-for="(section, index) in sections"
          :key="index"
          :title="section.title"
          :active="index === active"
        >
          <component
            :is="section.component"
          ></component>
        </app-tab>
      </app-tabs>
      ...
      <script setup>
      import { ref } from 'vue'
      
      const active = ref(0)
      </script>
      
      

      【讨论】:

        猜你喜欢
        • 2017-04-05
        • 2018-03-02
        • 2014-01-19
        • 2013-05-07
        • 2022-12-09
        • 2020-05-16
        • 2017-09-22
        • 2022-10-25
        • 1970-01-01
        相关资源
        最近更新 更多