You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 11:01:20