如何基于布尔值控制@headlessui/vue Popover组件的显隐
解决方案
你原本直接通过open属性控制的思路方向是对的,但需要改为受控绑定模式才能正常工作,@headlessui/vue 的 Popover 默认是非受控模式,单向绑定open属性无法同步组件内部触发的关闭动作(比如点击外部、按ESC),会出现状态不同步的问题。
现有代码的问题
- 你在
PopoverOverlay的类名中使用的open变量不存在,当前作用域没有声明该变量,也没有从Popover的插槽中获取内部状态 - 单向绑定
:open仅能从你的逻辑侧控制组件显隐,组件内部触发的关闭操作不会同步更新你的搜索结果状态,会出现「Popover已经关闭但下次输入相同关键词无法重新打开」的异常
修改后可运行的完整代码
<template> <form :action="action" method="GET" class="flex-1"> <!-- 改为v-model:open受控绑定显隐状态 --> <Popover v-model:open="isPopoverOpen" class="relative w-full"> <PopoverOverlay :class="`bg-black ${isPopoverOpen ? 'fixed inset-0 opacity-10' : 'opacity-0'}`" @click="emptySearchResults" /> <div class="relative"> <label for="search" class="sr-only">Search...</label> <input id="search" v-model="searchTerm" type="search" placeholder="Search..." class="h-10 w-full px-4 py-2 text-sm rounded-lg placeholder-gray-400 focus:outline-none" > </div> <PopoverPanel class="absolute top-full right-0 w-full mt-3 py-2 px-2 bg-gray-700 rounded-lg shadow-lg"> <a v-for="result in searchResults" :key="result.id" :href="result.url" class="flex items-center font-semibold p-2 text-sm text-white leading-none rounded-lg hover:bg-gray-600" > <span class="mr-3 py-1 px-2 text-xs text-gray-900 leading-none bg-gray-300 rounded-full"> {{ result.module }} </span> {{ result.title }} </a> </PopoverPanel> </Popover> </form> </template> <script setup> import { ref, watch, computed } from 'vue'; import { Popover, PopoverPanel } from '@headlessui/vue'; import debounce from '@/Helpers/debounce'; import xhr from '@/Helpers/xhr'; const searchTerm = ref(''); const searchResults = ref([]); // 受控模式的显隐状态,get关联搜索结果长度,set触发清空逻辑 const isPopoverOpen = computed({ get() { return searchResults.value.length > 0 }, set(isOpen) { // 当组件内部触发关闭时,自动清空搜索状态 if (!isOpen) { emptySearchResults() } } }) const getSearchResults = debounce(async (value) => { const { data } = await xhr.post('search', { q: value }); const { results } = await data; searchResults.value = results; }, 250); const emptySearchResults = () => { searchTerm.value = ''; searchResults.value = []; }; watch(searchTerm, (value) => { // 输入为空时直接清空结果关闭弹窗,不用走防抖 if (value.trim() === '') { emptySearchResults() return } getSearchResults(value) }); </script>
核心修改点
- 新增计算属性
isPopoverOpen做受控绑定的中间层,将Popover的显隐状态和搜索结果长度关联,同时处理组件内部关闭动作的同步逻辑 - Popover组件改为
v-model:open绑定isPopoverOpen,实现双向状态同步 - 优化searchTerm的监听逻辑,输入为空时直接清空状态,避免不必要的接口请求
内容的提问来源于stack exchange,提问作者japortegijs
相关产品推荐
相关产品推荐

