React可复用模块化下拉组件的两种实现方式及相关技术疑问
多级下拉菜单组件实现的技术疑问解答
我近期开发了一款追求高复用性的多级下拉菜单组件,采用配置式实现:通过传入定义菜单结构的对象来生成菜单,示例代码如下:
const content = [ { value: "Item 1", onClick: () => console.log("Clicked 1"), }, { value: "Item 2", children: [ { value: "Item 2.1", children: [ { value: "Item 2.1.1", onClick: () => mockFN, }, { value: "Item 2.1.2", onClick: () => mockFN, }, { value: "Item 2.1.3", }, { value: "Item 2.1.4", }, { value: "Item 2.1.5", onClick: () => mockFN, }, ], }, ], }, ]; return ( <Dropdown content={content} trigger={"Test"} /> );
用TypeScript约束数据结构:
type DropdownItem = { value: string; color?: string; textColor?: string; children?: DropdownItem[]; onClick?: () => void; };
同时注意到另一种组件组合式实现:通过暴露Dropdown.Item、Dropdown.Submenu等子组件,用JSX直接声明菜单结构,示例如下:
<Dropdown title='Dropdown title' > <Dropdown.Item onClick={() => doSomething()} > Item 1 </Dropdown.Item> <Dropdown.Item> Item 2 <Dropdown.Submenu> <Dropdown.Item> Subitem 1 </Dropdown.Item> </Dropdown.Submenu> </Dropdown.Item> </Dropdown>
针对这两种实现,以下是三个技术疑问的解答:
1. 两种组件结构方式的命名
- 配置式(数据驱动式):通过传入结构化数据(如数组/对象)动态生成组件内容,核心是用数据描述UI结构。
- 组合式(声明式):通过嵌套子组件的方式直接声明UI结构,符合React原生JSX写法,核心是组件的组合复用。
2. 如何选择实现方式?
没有绝对优先方案,完全取决于业务场景:
优先选配置式的场景:
- 菜单内容由后端接口返回,需要动态渲染;
- 菜单结构需要批量修改(如统一配置样式、批量添加权限校验);
- 需要通过逻辑动态生成菜单(如根据用户角色展示不同菜单项)。
你提到的后端返回数据场景,确实是配置式的天然优势,能快速将接口数据映射为菜单。
优先选组合式的场景:
- 菜单结构固定,不需要动态生成;
- 需要自定义单个菜单项的内容(如插入图标、多行文本、自定义DOM元素);
- 追求更直观的JSX写法,便于维护和阅读(尤其适合复杂交互的菜单项)。
3. Dropdown.Item 这类子组件的实现方式
Dropdown.Item 中的句号只是属性访问语法,并非组件名称的一部分,本质是把Item作为Dropdown组件的属性导出,实现方式分两种:
方式一:函数组件实现
import React from 'react'; // 实现子组件 const DropdownItem = ({ children, onClick, className }: { children: React.ReactNode; onClick?: () => void; className?: string; }) => { return ( <div className={`dropdown-item ${className || ''}`} onClick={onClick} > {children} </div> ); }; const DropdownSubmenu = ({ children }: { children: React.ReactNode }) => { return <div className="dropdown-submenu">{children}</div>; }; // 实现主Dropdown组件 const Dropdown = ({ title, children }: { title: string; children: React.ReactNode; }) => { return ( <div className="dropdown-container"> <button className="dropdown-trigger">{title}</button> <div className="dropdown-menu">{children}</div> </div> ); }; // 将子组件挂载到主组件上 Dropdown.Item = DropdownItem; Dropdown.Submenu = DropdownSubmenu; export default Dropdown;
方式二:类组件实现
import React, { Component } from 'react'; class DropdownItem extends Component { render() { const { children, onClick } = this.props; return <div onClick={onClick} className="dropdown-item">{children}</div>; } } class Dropdown extends Component { static Item = DropdownItem; // 将Item作为类的静态属性 static Submenu = class DropdownSubmenu extends Component { render() { return <div className="dropdown-submenu">{this.props.children}</div>; } }; render() { const { title, children } = this.props; return ( <div className="dropdown-container"> <button className="dropdown-trigger">{title}</button> <div className="dropdown-menu">{children}</div> </div> ); } } export default Dropdown;
两种方式的核心都是将子组件作为主组件的属性导出,既可以利用主组件的命名空间避免全局变量污染,又能让用户以Dropdown.Item的直观方式调用。
内容的提问来源于stack exchange,提问作者DylalyD
相关产品推荐
相关产品推荐

