首页
/ Ant Design 垂直菜单(mode="vertical")详解:让子菜单以弹出浮层形式呈现

Ant Design 垂直菜单(mode="vertical")详解:让子菜单以弹出浮层形式呈现

2026-09-07 18:37:42作者:盛欣凯Ernestine

导读

本篇文章以仓库中 Menu 垂直菜单示例 及其配套 示例源码 为线索,系统讲解 Ant Design(antd)Menu 组件 vertical 垂直模式的运行机制。你将掌握:verticalinlinehorizontal 三种模式在子菜单呈现方式上的本质差异、基于 items 数据结构组织多级子菜单与分组的写法、以及弹出式子菜单的交互参数(触发方式、开关延时、浮层样式)和底层实现。读完即可在自己的侧边导航/站点目录场景中写出可运行的垂直菜单。

示例背景:一段被“浓缩”的官方描述

components/menu/demo/vertical.md 是官方文档中垂直菜单示例的说明文案,全文非常精炼:

  • 中文:子菜单是弹出的形式。
  • 英文:Submenus open as pop-ups.

一句话点透了 vertical 模式区别于其他模式的唯一关键行为:处于垂直模式的菜单,其子菜单并不像 inline 模式那样在页面布局中内嵌展开,而是以“弹出浮层”(popup)的形式悬浮在菜单旁。在 Menu 组件文档 的代码演示列表中,该示例被标记为「垂直菜单」,同时还有「顶部导航」(horizontal)、「内嵌菜单」(inline)等兄弟示例,共同覆盖了 antd 支持的三种菜单模式。

完整示例代码与逐段拆解

与所有官方示例一样,说明文案对应的可运行代码存放在同目录下的 vertical.tsx 中:

import React from 'react';
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons';
import type { MenuProps } from 'antd';
import { Menu } from 'antd';

type MenuItem = Required<MenuProps>['items'][number];

const items: MenuItem[] = [
  {
    key: 'sub1',
    icon: <MailOutlined />,
    label: 'Navigation One',
    children: [
      {
        key: '1-1',
        label: 'Item 1',
        type: 'group',
        children: [
          { key: '1', label: 'Option 1' },
          { key: '2', label: 'Option 2' },
        ],
      },
      {
        key: '1-2',
        label: 'Item 2',
        type: 'group',
        children: [
          { key: '3', label: 'Option 3' },
          { key: '4', label: 'Option 4' },
        ],
      },
    ],
  },
  {
    key: 'sub2',
    icon: <AppstoreOutlined />,
    label: 'Navigation Two',
    children: [
      { key: '5', label: 'Option 5' },
      { key: '6', label: 'Option 6' },
      {
        key: 'sub3',
        label: 'Submenu',
        children: [
          { key: '7', label: 'Option 7' },
          { key: '8', label: 'Option 8' },
        ],
      },
    ],
  },
  {
    key: 'sub4',
    label: 'Navigation Three',
    icon: <SettingOutlined />,
    children: [
      { key: '9', label: 'Option 9' },
      { key: '10', label: 'Option 10' },
      { key: '11', label: 'Option 11' },
      { key: '12', label: 'Option 12' },
    ],
  },
];

const onClick: MenuProps['onClick'] = (e) => {
  console.log('click', e);
};

const App: React.FC = () => (
  <Menu onClick={onClick} style={{ width: 256 }} mode="vertical" items={items} />
);

export default App;

这段示例虽然短,却完整覆盖了垂直菜单的四个关键知识点:

  1. 开启垂直模式只用一个属性mode="vertical"。事实上 antd 的 Menu API 中 mode默认值就是 vertical(见 Menu 组件 API 表),因此当你不传 mode 时,得到的即是本示例所演示的弹出式子菜单行为。
  2. 固定容器宽度style={{ width: 256 }}。垂直菜单通常作为侧边栏出现,需要给容器一个确定宽度,使顶层菜单项的单行布局稳定。
  3. 数据驱动而非 JSX 嵌套:示例通过 items 数组 + type 字段描述菜单结构,这是 antd 4.20.0 起推荐的写法(menu.tsx 中对应 items 配置项)。
  4. 用类型约束 items 结构type MenuItem = Required<MenuProps>['items'][number]MenuProps 中提取单个菜单项的联合类型,保证后续编写的每一项都满足类型检查。

vertical 与 inline、horizontal:子菜单的三种“打开方式”

mode 接受 vertical | horizontal | inline 三种取值。要理解示例文案「子菜单是弹出的形式」为何值得单独强调,需要横向对比另外两种模式:

模式 典型使用场景 子菜单呈现方式 展开方向/表现 相关示例
vertical 侧边多级导航、站点目录 弹出浮层(popup) 触发父级后浮层从菜单一侧弹出,可继续在浮层中嵌套子菜单 垂直菜单
inline 侧边栏内嵌目录 页面内展开 点击后在当前布局内纵向展开,带动画收起 内嵌菜单
horizontal 顶部一级导航 弹出浮层(popup) 下拉浮层向下方展开 顶部导航

vertical.tsxinline.tsx 对比即可发现差异的根源:两者数据结构几乎一致,差异集中在两点——

  • inline 示例额外传了 defaultSelectedKeys={['1']}defaultOpenKeys={['sub1']},因为 inline 模式下“展开”意味着真实占用页面高度,所以提供“默认展开/选中哪一级”的受控起点;而垂直模式子菜单是浮层,不挤压下方菜单项空间,示例中不设置这两个值也毫无影响。
  • inline 示例在结构中插入了 { type: 'divider' } 分割线与独立的 type: 'group' 顶层分组,用来演示内嵌面板内的排版细节;vertical 示例则把 group 用在了弹出浮层内部。

文档 API 表也明确了两点差异(见 index.zh-CN.md):

  • popupClassNamepopupOffsetpopupRender 等浮层定制属性说明中都标注「mode="inline" 时无效」——因为 inline 根本不产生浮层,这些属性天然只服务于 vertical/horizontal 的弹出子菜单。
  • inlineCollapsedinlineIndent 仅对 inline 模式有意义;menu.tsx 中甚至对该误用直接给出 dev 警告:'inlineCollapsed' should only be used when mode is inline.(见 menu.tsx)。

items 数据模型:子菜单、分组与三级嵌套

示例的 items 用统一的对象字面量描述了三种语义节点,它们对应 interface.ts 中导出的 ItemType 联合类型:MenuItemType | SubMenuType | MenuItemGroupType | MenuDividerType

第一层:顶层子菜单(SubMenu)。 sub1sub2sub4 均带 iconlabel,并通过 children 承载下级节点。它们就是文案所说的“会弹出来”的父级——把鼠标悬停/点击在任一父级上,就会从菜单侧边弹出浮层。

第二层:分组(group)与菜单项(item)。 sub1 的浮层内部是两个 type: 'group' 分组,每个分组有自己的标题(Item 1/Item 2)与若干叶子项。分组只是视觉分隔标题,不带 key 交互行为;叶子项才携带 key(如 12…),作为选中、事件回传的唯一标识。文档中对 group 的写法有明确规定(见 index.zh-CN.mdMenuItemGroupType 章节):type: 'group' 是必填项。

第三层:子菜单内的子菜单(submenu 套 submenu)。 sub2 的浮层里嵌套了 sub3(Submenu),sub3 又含有 Option 7/8 两个叶子项。这验证了垂直模式浮层具有多级递归能力:当鼠标移入 sub3 时,会再从 sub2 的浮层侧边弹出更下一级浮层。子菜单类型在 interface.tsSubMenuType 中被定义,其 children 类型依旧是 ItemType<T>[],因此递归嵌套在类型层面是被天然支持的。

需要特别说明的是分组节点在三种模式下的形态差异:在 inline 内嵌面板中,group 标题会作为常驻的段落小标题直接排在页面流中;而在 vertical 的弹出浮层中,group 标题仅作为浮层内部的分组分隔,这也是示例把 group 全部放进子菜单 children 的原因。

交互行为与事件回调:垂直模式的展开与选中

示例通过 onClick 把点击事件打印到控制台:

const onClick: MenuProps['onClick'] = (e) => {
  console.log('click', e);
};

配合 Menu 的 API(见 index.zh-CN.md 的 API 表),可以梳理出垂直菜单的完整交互模型:

  • 展开触发triggerSubMenuAction 默认 hover,即鼠标移入父级即弹出浮层,可改为 click。考虑到移动端或触屏没有 hover,切换到 click 触发是常见适配手段。
  • 开关延时subMenuOpenDelay 默认 0 秒立即弹出,subMenuCloseDelay 默认 0.1 秒,后者用于防止鼠标在移向浮层的过程中误触关闭,是弹出式子菜单特有的“防抖”参数。
  • 选中与回调:叶子项可被选中(selectable 默认 true)。onClick 收到的参数形如 { key, keyPath, domEvent, itemData },其中 keyPath 会给出从叶子到根部的完整 key 链路,便于在处理点击时知道用户落在哪个子菜单层级下;此外还有 onSelectonDeselectmultiple 模式)、onOpenChange 等回调。
  • 受控展开:若需要程序化控制哪些父级处于展开态,可用 openKeys 配合 onOpenChange;首次渲染态则用 defaultOpenKeys。需要提醒的是,由于 vertical 浮层随鼠标出现/消失,受控 openKeys 往往与 hover 触发配合使用才会符合直觉,实战中更常见的做法是交给组件内部管理。

源码级佐证:弹出式子菜单是怎么“弹”出来的

Ant Design 的 Menu 是基于 @rc-component/menu 二次封装而成(见 menu.tsx)。从源码中可以找到垂直模式浮层行为的实现证据:

  • 不同的子菜单展开动画:menu.tsx 为三种模式注册了不同的 defaultMotions——horizontal 使用 slide-up 位移动效,inline 使用 initCollapseMotion(高度折叠动画),而 vertical 归入 other 分支,使用 ${rootPrefixCls}-zoom-big 缩放动效。也就是说,垂直模式子菜单“弹出来”时伴随的是浮层缩放显现,这是它作为脱离文档流的 popup 在视觉上的直接体现。
  • 子菜单通过浮层承载:antd 将 item、submenu、divider 三类节点分别映射到内部组件(MENU_COMPONENTS),SubMenu 组件内部会把非 inline 模式的子内容渲染到基于 getPopupContainer 定位的浮层容器中。文档中 popupClassName(定制浮层样式)、popupOffset(调整浮层与父级的偏移)、popupRender(完全重写浮层内容)等参数都作用于该容器,且均标注对 inline 无效,进一步印证“vertical 的子菜单 = 浮层”这一行为边界。
  • 节点结构二次渲染的取舍:FAQ 中说明 Menu 会通过二次渲染收集嵌套结构信息以支持 HOC 型子节点,这也是为什么更推荐直接使用 items 对象数组描述结构(本示例即采用该写法),避免 JSX 深层嵌套带来的渲染与维护成本。

实战要点小结

  • 想要“侧边多级导航且子菜单悬浮展开”的效果,设置 mode="vertical"(或不传 mode,因为默认即为 vertical)即可,容器请给定 width
  • 子菜单天然支持多级与分组:父级节点写 children,分组节点写 type: 'group',叶子项保证 key 唯一。
  • 浮层相关定制属性(popupClassNamepopupOffsetpopupRender、开关延时、触发方式)只对弹出式子菜单生效;需要“页面内展开”的侧边目录请改用 mode="inline",折叠需求则参考 缩起内嵌菜单示例,多模式动态切换可参考 切换菜单类型示例
  • 事件处理上统一通过 onClick/onSelect 拿到 { key, keyPath, ... },垂直浮层不参与页面布局,天然适合作为不挤压内容区的多级导航方案。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388