如何单独控制Headless UI Disclosure的展开与收起状态
问题解决:Headless UI Disclosure 组件同步展开问题
问题描述
使用Headless UI的Disclosure组件时,打开一个折叠面板,其他所有面板会同步展开(虽内容不显示但存在空白占位),需要实现单独控制每个面板的展开/收起状态。
问题原因
出现这种同步展开的情况,通常是因为:
- 多个Disclosure实例的状态被意外共享
- 循环渲染CardValue组件时未指定唯一
key,导致React复用组件实例,引发状态混乱
修复方案
提供两种可靠解决方式,可根据需求选择:
方式一:循环渲染时添加唯一key
如果通过数组循环渲染多个CardValue,必须给每个组件添加唯一key属性,让React能区分不同组件实例,避免状态复用。
示例代码:
{cardList.map((card) => ( <CardValue key={card.id} // 使用数据的唯一ID作为key icon={<ChartBarIcon className='text-white h-5 w-5' />} title={card.title} subtitle={card.subtitle} // 其他属性... > {/* 子内容 */} </CardValue> ))}
方式二:手动管理Disclosure状态(灵活可控)
通过父组件传递状态和回调,让每个CardValue的展开状态独立可控,彻底避免状态共享问题。
修改后的CardValue组件
import React from 'react'; import { Disclosure } from '@headlessui/react'; import { ChevronUpIcon } from '@heroicons/react/solid'; import { Ratio } from '../ratio/Ratio'; export const CardValue = ({ icon, title, subtitle, children, subtitleClassName, selected, arrow, arrowValue, // 新增手动控制的状态与回调 isOpen, onToggle, }) => { return ( <div className='w-full max-w-[325px] rounded-lg bg-white p-2'> {/* 传入手动状态与回调 */} <Disclosure open={isOpen} onOpenChange={onToggle}> {({ open }) => ( <> <Disclosure.Button className='flex w-full justify-between rounded-lg px-4 py-2 text-left text-sm font-medium focus:outline-none'> <div className='bg-[#01B5A3] rounded-lg p-2'>{icon}</div> <div className='flex flex-col space-y-3'> <label className='text-lg'>{title}</label> <label className={`text-2xl font-semibold ${subtitleClassName}`}> {subtitle} </label> </div> <div className='flex flex-col space-y-5 items-center'> <ChevronUpIcon className={`${ open ? 'rotate-180 transform' : '' } h-5 w-5 text-[#727272]`} /> </div> </Disclosure.Button> <Disclosure.Panel className='px-4 pt-4 pb-2 text-sm text-gray-500'> {children} </Disclosure.Panel> </> )} </Disclosure> </div> ); };
父组件中使用示例
import React, { useState } from 'react'; import { CardValue } from './CardValue'; import { ChartBarIcon } from '@heroicons/react/solid'; // 示例数据列表 const cardData = [ { id: 1, title: 'Sales Converted', total: 1200 }, { id: 2, title: 'Leads Generated', total: 340 }, // 更多数据... ]; const ParentComponent = () => { // 用对象存储每个卡片的展开状态 const [openStates, setOpenStates] = useState({}); const handleToggle = (cardId) => { setOpenStates(prev => ({ ...prev, [cardId]: !prev[cardId] })); }; return ( <div className='flex flex-wrap gap-4'> {cardData.map((card) => ( <CardValue key={card.id} icon={<ChartBarIcon className='text-white h-5 w-5' />} title={card.title} subtitle={`${detailStore?.currency} ${ card.total == 0 ? 'N/A' : card.total }`} subtitleClassName={`${ card.total == 0 ? 'text-na_text' : 'text-black' }`} isOpen={openStates[card.id] || false} onToggle={() => handleToggle(card.id)} > {/* 子内容 */} <hr /> <div className='flex flex-1 flex-col mt-6 mb-4 space-y-4 text-sm'> {/* 子内容项 */} </div> </CardValue> ))} </div> ); }; export default ParentComponent;
方案说明
- 方式一适合简单场景,仅需保证循环渲染时
key唯一即可 - 方式二更适合需要精准控制面板状态的场景,比如默认展开指定面板、联动控制多个面板等
内容的提问来源于stack exchange,提问作者Muhammad Haekal
相关产品推荐
相关产品推荐

