这是我在 Tabs 组件中使用 VUE 2 的旧代码:
created() {
this.tabs = this.$children;
}
标签:
<Tabs>
<Tab title="tab title">
....
</Tab>
<Tab title="tab title">
....
</Tab>
</Tabs>
VUE 3: 如何使用组合 API 获取有关 Tabs 组件中子项的一些信息?获取长度,迭代它们,并创建选项卡标题,...等?有任何想法吗? (使用组合API)
这是我现在的 Vue 3 组件。我使用 Provide 来获取子
Tab
组件中的信息。
<template>
<div class="tabs">
<div class="tabs-header">
<div
v-for="(tab, index) in tabs"
:key="index"
@click="selectTab(index)"
:class="{'tab-selected': index === selectedIndex}"
class="tab"
>
{{ tab.props.title }}
</div>
</div>
<slot></slot>
</div>
</template>
<script lang="ts">
import {defineComponent, reactive, provide, onMounted, onBeforeMount, toRefs, VNode} from "vue";
interface TabProps {
title: string;
}
export default defineComponent({
name: "Tabs",
setup(_, {slots}) {
const state = reactive({
selectedIndex: 0,
tabs: [] as VNode<TabProps>[],
count: 0
});
provide("TabsProvider", state);
const selectTab = (i: number) => {
state.selectedIndex = i;
};
onBeforeMount(() => {
if (slots.default) {
state.tabs = slots.default().filter((child) => child.type.name === "Tab");
}
});
onMounted(() => {
selectTab(0);
});
return {...toRefs(state), selectTab};
}
});
</script>
选项卡组件:
<script lang="ts">
export default defineComponent({
name: "Tab",
setup() {
const index = ref(0);
const isActive = ref(false);
const tabs = inject("TabsProvider");
watch(
() => tabs.selectedIndex,
() => {
isActive.value = index.value === tabs.selectedIndex;
}
);
onBeforeMount(() => {
index.value = tabs.count;
tabs.count++;
isActive.value = index.value === tabs.selectedIndex;
});
return {index, isActive};
}
});
</script>
<template>
<div class="tab" v-show="isActive">
<slot></slot>
</div>
</template>
哦伙计们,我解决了:
this.$slots.default().filter(child => child.type.name === 'Tab')
对于想要完整代码的人:
标签.vue
<template>
<div>
<div class="tabs">
<ul>
<li v-for="tab in tabs" :class="{ 'is-active': tab.isActive }">
<a :href="tab.href" @click="selectTab(tab)">{{ tab.name }}</a>
</li>
</ul>
</div>
<div class="tabs-details">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: "Tabs",
data() {
return {tabs: [] };
},
created() {
},
methods: {
selectTab(selectedTab) {
this.tabs.forEach(tab => {
tab.isActive = (tab.name == selectedTab.name);
});
}
}
}
</script>
<style scoped>
</style>
标签.vue
<template>
<div v-show="isActive"><slot></slot></div>
</template>
<script>
export default {
name: "Tab",
props: {
name: { required: true },
selected: { default: false}
},
data() {
return {
isActive: false
};
},
computed: {
href() {
return '#' + this.name.toLowerCase().replace(/ /g, '-');
}
},
mounted() {
this.isActive = this.selected;
},
created() {
this.$parent.tabs.push(this);
},
}
</script>
<style scoped>
</style>
应用程序.js
<template>
<Tabs>
<Tab :selected="true"
:name="'a'">
aa
</Tab>
<Tab :name="'b'">
bb
</Tab>
<Tab :name="'c'">
cc
</Tab>
</Tabs>
<template/>
我扫描子元素的解决方案(在对 vue 代码进行大量筛选之后)是这样的。
export function findChildren(parent, matcher) {
const found = [];
const root = parent.$.subTree;
walk(root, child => {
if (!matcher || matcher.test(child.$options.name)) {
found.push(child);
}
});
return found;
}
function walk(vnode, cb) {
if (!vnode) return;
if (vnode.component) {
const proxy = vnode.component.proxy;
if (proxy) cb(vnode.component.proxy);
walk(vnode.component.subTree, cb);
} else if (vnode.shapeFlag & 16) {
const vnodes = vnode.children;
for (let i = 0; i < vnodes.length; i++) {
walk(vnodes[i], cb);
}
}
}
这将返回子组件。我对此的用途是我有一些通用的对话框处理代码,用于搜索子表单元素组件以咨询其有效性状态。
const found = findChildren(this, /^(OSelect|OInput|OInputitems)$/);
const invalid = found.filter(input => !input.checkHtml5Validity());
如果你复制粘贴与我相同的代码
然后只需向“选项卡”组件添加一个创建的方法,该方法将自身添加到其父级的选项卡数组中
created() {
this.$parent.tabs.push(this);
},
使用脚本设置语法,您可以使用
useSlots
:https://vuejs.org/api/sfc-script-setup.html#useslots-useattrs
<script setup>
import { useSlots, ref, computed } from 'vue';
const props = defineProps({
perPage: {
type: Number,
required: true,
},
});
const slots = useSlots();
const amountToShow = ref(props.perPage);
const totalChildrenCount = computed(() => slots.default()[0].children.length);
const childrenToShow = computed(() => slots.default()[0].children.slice(0, amountToShow.value));
</script>
<template>
<component
:is="child"
v-for="(child, index) in childrenToShow"
:key="`show-more-${child.key}-${index}`"
></component>
</template>
我对 Ingrid Oberbüchler 的组件做了一个小改进,因为它不支持热重载/动态选项卡。
在 Tab.vue 中:
onBeforeMount(() => {
// ...
})
onBeforeUnmount(() => {
tabs.count--
})
在 Tabs.vue 中:
const selectTab = // ...
// ...
watch(
() => state.count,
() => {
if (slots.default) {
state.tabs = slots.default().filter((child) => child.type.name === "Tab")
}
}
)
我也遇到了同样的问题,在做了很多研究并问自己为什么他们删除了
$children
之后,我发现他们创建了一个更好、更优雅的替代方案。
这是关于动态组件的。 (
<component: is =" currentTabComponent "> </component>
).
我在这里找到的信息:
https://v3.vuejs.org/guide/component-basics.html#dynamic-components
希望这对你有用,向大家问好!!
我发现这个更新的 Vue3 教程使用 Vue 插槽构建可重用的选项卡组件对于与我相关的解释非常有帮助。
它使用 ref、provide 和ject 来替换我遇到同样问题的
this.tabs = this.$children;
。
我一直在遵循我最初发现的构建选项卡组件(Vue2)的教程的早期版本创建您自己的可重用 Vue 选项卡组件。
根据 Vue 文档,假设您在
Tabs
组件下有一个默认插槽,您可以直接在模板中访问该插槽的子级,如下所示:
// 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>
另一种方法是使用
useSlots
,如 Vue 文档(上面的链接)中所述。
在 3.x 中,$children 属性已被删除并且不再受支持。相反,如果您需要访问子组件实例,他们建议使用 $refs。作为数组
https://v3-migration.vuejs.org/writing-changes/children.html#_2-x-syntax
在 3.x 版本中,
$children
已被删除且不再受支持。使用 ref 访问子实例。
<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent .vue'
const child = ref(null)
onMounted(() => {
console.log(child.value) // log an instance of <Child />
})
</script>
<template>
<ChildComponent ref="child" />
</template>
详细信息:https://vuejs.org/guide/essentials/template-refs.html#template-refs
基于@Urkle的回答:
/**
* walks a node down
* @param vnode
* @param cb
*/
export function walk(vnode, cb) {
if (!vnode) return;
if (vnode.component) {
const proxy = vnode.component.proxy;
if (proxy) cb(vnode.component.proxy);
walk(vnode.component.subTree, cb);
} else if (vnode.shapeFlag & 16) {
const vnodes = vnode.children;
for (let i = 0; i < vnodes.length; i++) {
walk(vnodes[i], cb);
}
}
}
除了已接受的答案之外:
而不是
this.$root.$children.forEach(component => {})
写
walk(this.$root, component => {})
这就是我让它为我工作的方式。