如何在 Vue 3 中访问 $children 以创建选项卡组件?
Posted
技术标签:
【中文标题】如何在 Vue 3 中访问 $children 以创建选项卡组件?【英文标题】:How to access $children in Vue 3 for creating a Tabs component? 【发布时间】:2021-04-09 16:20:43 【问题描述】:我正在尝试在 Vue 3 中创建一个类似于 question here 的 Tabs
组件。
<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>
不幸的是,当内部的Tab
s 是动态的,即如果Tab
上有一个v-if 或者当Tab
s 使用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
等?
【问题讨论】:
【参考方案1】:这看起来像是将项目索引用作v-for
循环的key
的问题。
第一个问题是您将v-for
的key
应用到了应该在父元素上的子元素(在本例中为<li>
)。
<li v-for="(tab, i) in tabs">
<a :key="i"> ❌
</a>
</li>
此外,如果 v-for
后备数组可以重新排列其项目(或删除中间项目),请不要将项目索引用作 key
,因为该索引不会提供始终如一的唯一值。例如,如果从列表中删除了 3 项中的第 2 项,则第三项将向上移动到索引 1,采用之前被删除项使用的 key
。由于列表中没有key
s 发生变化,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 中的 <tab :title="t.title" v-for="t in more" :key="t">Dynamic t </tab>
。有什么想法吗?
还有一条评论。在 Vue 2 中,不需要使用 title
或其他任何内容“键入”选项卡,因为 $children
提供了对插槽子项的直接访问。所以<Tabs><Tab>1</Tab><Tab>2</Tab></Tabs>
完全没问题。现在看来我们需要在每个<Tab>
上指定一个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,看起来是正确的方法。缺少的一块拼图是getCurrentInstance
在setup
期间可用
完整的工作代码可以在这里找到:
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>
实现应该类似于以下内容(考虑一个对象数组,每个部分一个,带有title
和component
):
...
<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>
【讨论】:
以上是关于如何在 Vue 3 中访问 $children 以创建选项卡组件?的主要内容,如果未能解决你的问题,请参考以下文章
Vue中如何存储多个children的值并在父组件中执行一个函数
在 vue.js 组件中反应 this.props.children