Vue3可复用Collapse组件父子状态同步异常问题求助
Vue3 Collapse组件父子状态不同步问题解决(无需状态管理)
问题根源
你的代码里,子组件Collapse维护了独立的isOpen状态,内部切换时只更新了自身的isOpen,没有同步回父组件的collapsePanels数据。当父组件再次尝试切换时,第一次修改的isVisible值和子组件当前的isOpen完全一致,导致watch监听不到变化,必须点击第二次才能触发状态更新。
两种可行解决方案
方案一:用Vue v-model语法糖实现双向绑定
这是最简洁的方式,利用Vue的v-model自定义事件机制,自动同步父子状态。
修改Collapse.vue组件
<script setup> const props = defineProps({ title: { type: String, required: true, }, collapsed: { type: Boolean, default: false, }, }); // 定义用于更新父组件状态的emit事件 const emit = defineEmits(['update:collapsed']); // 用computed实现双向绑定:读取取props,修改触发emit const isOpen = computed({ get() { return props.collapsed; }, set(value) { emit('update:collapsed', value); } }); const toggleCollapse = () => { isOpen.value = !isOpen.value; }; </script> <template> <div class="flex flex-col ml-[20px] mb-2"> <button @click="toggleCollapse" class="text-left flex items-center relative font-source text-[19px] sm:text-[16px] lg:text-[19px] mb-2 font-semibold" > <IconCSS :class="{ 'rotate-90': isOpen }" class="text-3xl text-green absolute -left-7" name="mdi:chevron-right" /> {{ title }} </button> <div v-if="isOpen"> <slot name="collapseContent"></slot> </div> </div> </template>
修改父组件调用代码
把原来的:collapsed绑定改成v-model:collapsed,自动同步状态:
<script setup> const collapsePanels = ref([ { id: 0, isVisible: false }, { id: 1, isVisible: false }, { id: 2, isVisible: false } ]); const openCollapse = (id) => { collapsePanels.value[id].isVisible = !collapsePanels.value[id].isVisible; }; </script> <template> <button @click="openCollapse(0)">Toggle Panel One</button> <button @click="openCollapse(2)">Toggle Panel Three</button> <Collapse v-model:collapsed="collapsePanels[0].isVisible" title="Collapse One"> <template v-slot:collapseContent> <p>Collpase One Content</p> </template> </Collapse> <Collapse title="Collapse Two"> <template v-slot:collapseContent> <p>Collpase Two Content</p> </template> </Collapse> <Collapse v-model:collapsed="collapsePanels[2].isVisible" title="Collapse Three"> <template v-slot:collapseContent> <p>Collpase Three Content</p> </template> </Collapse> </template>
方案二:手动触发事件同步状态
如果不想用v-model,也可以在子组件切换时手动触发事件,让父组件更新对应状态。
修改Collapse.vue组件
<script setup> const props = defineProps({ title: { type: String, required: true, }, collapsed: { type: Boolean, default: false, }, }); // 定义toggle事件通知父组件 const emit = defineEmits(['toggle']); // 直接使用props的collapsed作为状态,无需独立的isOpen const toggleCollapse = () => { emit('toggle'); }; </script> <template> <div class="flex flex-col ml-[20px] mb-2"> <button @click="toggleCollapse" class="text-left flex items-center relative font-source text-[19px] sm:text-[16px] lg:text-[19px] mb-2 font-semibold" > <IconCSS :class="{ 'rotate-90': collapsed }" class="text-3xl text-green absolute -left-7" name="mdi:chevron-right" /> {{ title }} </button> <div v-if="collapsed"> <slot name="collapseContent"></slot> </div> </div> </template>
修改父组件代码
监听子组件的toggle事件,调用已有的openCollapse方法更新状态:
<script setup> const collapsePanels = ref([ { id: 0, isVisible: false }, { id: 1, isVisible: false }, { id: 2, isVisible: false } ]); const openCollapse = (id) => { collapsePanels.value[id].isVisible = !collapsePanels.value[id].isVisible; }; </script> <template> <button @click="openCollapse(0)">Toggle Panel One</button> <button @click="openCollapse(2)">Toggle Panel Three</button> <Collapse :collapsed="collapsePanels[0].isVisible" title="Collapse One" @toggle="openCollapse(0)" > <template v-slot:collapseContent> <p>Collpase One Content</p> </template> </Collapse> <Collapse title="Collapse Two"> <template v-slot:collapseContent> <p>Collpase Two Content</p> </template> </Collapse> <Collapse :collapsed="collapsePanels[2].isVisible" title="Collapse Three" @toggle="openCollapse(2)" > <template v-slot:collapseContent> <p>Collpase Three Content</p> </template> </Collapse> </template>
总结
两种方案都能解决状态不同步的问题,核心思路都是让子组件的状态完全依赖父组件,或者在子组件状态变化时同步更新父组件,避免出现父子状态脱节的情况。
内容的提问来源于stack exchange,提问作者MonTOne
相关产品推荐
相关产品推荐

