如何改造自定义React Dropdown组件实现Body的显隐控制?
改造Dropdown组件的两种可行方案
方案一:React Context 状态共享(推荐)
这是react-bootstrap同款实现思路,能完美支持任意层级的子组件共享Dropdown状态,适合复杂嵌套场景。
- 先创建Dropdown专属Context
import { createContext, useContext, useState, ReactNode } from 'react'; // 定义上下文数据结构 const DropdownContext = createContext<{ isOpen: boolean; toggleOpen: () => void; } | null>(null); // 封装自定义Hook,简化子组件调用 const useDropdown = () => { const context = useContext(DropdownContext); if (!context) { throw new Error('Dropdown.Header/Dropdown.Body必须在Dropdown组件内部使用'); } return context; };
- 改造Dropdown父组件,提供上下文
type DropdownProps = { children: ReactNode; defaultOpen?: boolean; }; const Dropdown = ({ children, defaultOpen = false }: DropdownProps) => { const [isOpen, setIsOpen] = useState(defaultOpen); const toggleOpen = () => setIsOpen(prev => !prev); return ( <DropdownContext.Provider value={{ isOpen, toggleOpen }}> <div className="dropdown-container">{children}</div> </DropdownContext.Provider> ); };
- 实现Header和Body组件
// Header组件无需状态,直接渲染内容 const DropdownHeader = ({ children }: { children: ReactNode }) => { return <div className="dropdown-header">{children}</div>; }; // Body组件通过Hook获取isOpen控制显示 const DropdownBody = ({ children }: { children: ReactNode }) => { const { isOpen } = useDropdown(); return isOpen ? <div className="dropdown-body">{children}</div> : null; }; // 挂载到Dropdown上,保持调用一致性 Dropdown.Header = DropdownHeader; Dropdown.Body = DropdownBody; Dropdown.Toggle = ({ children }: { children: ReactNode }) => { const { toggleOpen } = useDropdown(); return <button onClick={toggleOpen} className="dropdown-toggle">{children}</button>; };
- 使用示例
<Dropdown> <Dropdown.Toggle>点击展开菜单</Dropdown.Toggle> <Dropdown.Header>用户操作菜单</Dropdown.Header> <Dropdown.Body> <div> <p>可以放任意React元素</p> <button>修改资料</button> <button>退出登录</button> </div> </Dropdown.Body> </Dropdown>
方案二:克隆子组件传递Props(适合简单场景)
如果你的Dropdown结构固定(Toggle/Header/Body直接作为Dropdown子元素),可以直接通过克隆子组件传递状态,无需Context。
- 改造Dropdown父组件
const Dropdown = ({ children, defaultOpen = false }: DropdownProps) => { const [isOpen, setIsOpen] = useState(defaultOpen); const toggleOpen = () => setIsOpen(prev => !prev); // 遍历子组件,给对应类型传递props const renderedChildren = React.Children.map(children, child => { if (!React.isValidElement(child)) return child; if (child.type === DropdownBody) { return React.cloneElement(child, { isOpen }); } if (child.type === Dropdown.Toggle) { return React.cloneElement(child, { onClick: toggleOpen }); } return child; }); return <div className="dropdown-container">{renderedChildren}</div>; };
- 调整Dropdown.Body组件
const DropdownBody = ({ children, isOpen }: { children: ReactNode; isOpen: boolean }) => { return isOpen ? <div className="dropdown-body">{children}</div> : null; };
方案对比
- Context方案:灵活性拉满,支持任意嵌套层级,是生产环境首选。
- 克隆子组件方案:实现简单,但仅支持扁平结构的子组件,扩展性差。
内容的提问来源于stack exchange,提问作者Iavor Orlyov
相关产品推荐
相关产品推荐

