如何在 Vue 3 中访问 $children 以创建 Tabs 组件?
How to access $children in Vue 3 for creating a Tabs component?
我正在尝试在 Vue 3 中创建一个类似于 .
的 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
内部是动态的,即如果 Tab
上有 v-if 或 Tab
被渲染时,建议的解决方案不起作用使用 v-for
循环 - 它失败了。
我在这里为它创建了一个 Codesandbox,因为它包含 .vue
个文件:
https://codesandbox.io/s/sleepy-mountain-wg0bi?file=%2Fsrc%2FApp.vue
我试过像 onBeforeMount
一样使用 onBeforeUpdate
,但这也不起作用。实际上,它确实插入了新的标签,但是标签的顺序改变了。
最大的障碍似乎是在 Vue 3 中似乎无法 get/set child
来自父级的数据。(如 $children
在 Vue 2.x)。有人建议使用 this.$.subtree.children
但后来强烈建议不要使用(无论如何我试过都没有帮助)。
谁能告诉我如何使 Tabs
中的 Tab
响应并更新 v-if
等?
这看起来像是使用项目索引作为 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
发生变化,Vue 重用现有的虚拟 DOM 节点作为优化,不会发生重新渲染。
在您的情况下,一个好的 key
到 select 是选项卡的 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>
这个解决方案是 posted by @anteriovieira 在 Vuejs 论坛中,看起来是正确的方法。缺失的拼图 getCurrentInstance
在 setup
期间可用
完整的工作代码可以在这里找到:
https://codesandbox.io/s/vue-3-tabs-ob1it
我将其添加到此处以供来自 Google 的任何人参考。
由于模板中的 $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 中创建一个类似于
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
内部是动态的,即如果 Tab
上有 v-if 或 Tab
被渲染时,建议的解决方案不起作用使用 v-for
循环 - 它失败了。
我在这里为它创建了一个 Codesandbox,因为它包含 .vue
个文件:
https://codesandbox.io/s/sleepy-mountain-wg0bi?file=%2Fsrc%2FApp.vue
我试过像 onBeforeMount
一样使用 onBeforeUpdate
,但这也不起作用。实际上,它确实插入了新的标签,但是标签的顺序改变了。
最大的障碍似乎是在 Vue 3 中似乎无法 get/set child
来自父级的数据。(如 $children
在 Vue 2.x)。有人建议使用 this.$.subtree.children
但后来强烈建议不要使用(无论如何我试过都没有帮助)。
谁能告诉我如何使 Tabs
中的 Tab
响应并更新 v-if
等?
这看起来像是使用项目索引作为 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
发生变化,Vue 重用现有的虚拟 DOM 节点作为优化,不会发生重新渲染。
在您的情况下,一个好的 key
到 select 是选项卡的 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>
这个解决方案是 posted by @anteriovieira 在 Vuejs 论坛中,看起来是正确的方法。缺失的拼图 getCurrentInstance
在 setup
完整的工作代码可以在这里找到:
https://codesandbox.io/s/vue-3-tabs-ob1it
我将其添加到此处以供来自 Google 的任何人参考。
由于模板中的 $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>