首页
/ Ant Design Menu 导航菜单组件完全指南:从三种布局模式、items 配置到源码级原理

Ant Design Menu 导航菜单组件完全指南:从三种布局模式、items 配置到源码级原理

2026-09-07 16:57:27作者:管翌锬

导航菜单是任何 Web 站点不可或缺的组成部分,一套组织良好的导航可以让用户在站内快速、高效地定位目标内容。作为 React 生态中使用最广泛的企业级组件库之一,ant-design 仓库中的 components/menu 为开发者提供了一款功能完善、形态丰富(顶部导航 / 侧边导航 / 内嵌导航)的 Menu 组件,并配套了受控选中、多选、分组、分隔线、子菜单浮层、折叠态 Tooltip、暗色主题以及语义化定制等能力。

本文以仓库内官方文档 components/menu/index.en-US.md 为骨架,结合 components/menu 目录下的源码实现与官方示例(demo/*.tsx),系统讲解 Menu 的核心 API、ItemType 配置体系、与 Sider 布局的联动、FAQ 中的典型坑点以及 v6 引入的语义化 DOM 定制能力。读完后你将能够直接照着示例搭建可用的顶部/侧边导航,并理解其渲染结构为何如此设计的底层原因。

何时使用(When To Use)

导航是任何网站的重要组成部分,良好的导航设置能让用户快速、高效地在站内移动。Ant Design 提供两种导航形态,分别对应两类站点结构:

  • 顶部导航(Top navigation):将站点的所有分类与功能平铺展示,适合信息架构偏扁平的站点(通常对应 mode="horizontal")。
  • 侧边导航(Side navigation):承载站点的多层级结构,适合层级深、菜单项多的场景(通常对应 mode="inline"mode="vertical")。

菜单本身通常与布局组件配合使用,更多导航相关布局示例可以参考 Layout 布局组件文档

面向开发者的两条重要说明

在使用 Menu 之前,有两个影响写法的约束需要先明确(原文档 "Notes for developers" 一节):

  1. Menu 被渲染为 ul 元素,因此它只允许 li 以及 script-supporting 元素作为直接子节点。你自定义的任何节点都必须包在 Menu.Item(或其子类组件)内部,不能直接把任意自定义元素当作 Menu 的直接 children。
  2. Menu 需要收集自身的节点结构(例如 key、路径信息、父子层级关系),因此它的 children 应当是 Menu.* 系列组件,或者是基于这些组件封装的高阶组件(HOC)。

这两条约束直接决定了 Menu 内部“收集结构信息”的实现方式,详见下文“FAQ:为什么 Menu 的 children 会被渲染两次”。

从官方示例出发:先跑通常见形态

原文档通过 demo/ 目录挂载了一批可直接运行的示例,覆盖了 Menu 的全部典型使用场景。以下按使用频率梳理这些形态,示例代码与说明文案分别位于 components/menu/demodemo/*.md 文件中。

顶部导航:horizontal 模式

顶部导航示例见 components/menu/demo/horizontal.tsx。核心做法是用 items 数组声明菜单结构,再配合 selectedKeys 受控当前项:

import React, { useState } 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[] = [
  { label: 'Navigation One', key: 'mail', icon: <MailOutlined /> },
  { label: 'Navigation Two', key: 'app', icon: <AppstoreOutlined />, disabled: true },
  {
    label: 'Navigation Three - Submenu',
    key: 'SubMenu',
    icon: <SettingOutlined />,
    children: [
      { type: 'group', label: 'Item 1', children: [
        { label: 'Option 1', key: 'setting:1' },
        { label: 'Option 2', key: 'setting:2' },
      ] },
      { type: 'group', label: 'Item 2', children: [
        { label: 'Option 3', key: 'setting:3' },
        { label: 'Option 4', key: 'setting:4' },
      ] },
    ],
  },
  { key: 'alipay', label: <a href="https://ant.design" target="_blank" rel="noopener noreferrer">Navigation Four - Link</a> },
];

const App: React.FC = () => {
  const [current, setCurrent] = useState('mail');
  const onClick: MenuProps['onClick'] = (e) => {
    setCurrent(e.key);
  };
  return <Menu onClick={onClick} selectedKeys={[current]} mode="horizontal" items={items} />;
};

示例中还演示了两个常见细节:

  • 菜单项可通过 disabled: true 置灰(示例中的 "Navigation Two")。
  • 菜单项的 label 可以直接放置 <a> 链接,此时点击事件仍可通过 onClick 回调拿到 key
  • 注意使用 type MenuItem = Required<MenuProps>['items'][number] 约束 items 结构,可获得完整的 TypeScript 提示。

仓库中还提供了顶栏暗色变体 components/menu/demo/horizontal-dark.tsx(文档标记为 debug,主要用于内部调试)。

内嵌菜单:inline 模式

内嵌菜单通常用于 Sider 布局中,示例见 components/menu/demo/inline.tsx

const items: MenuItem[] = [
  {
    key: 'sub1', label: 'Navigation One', icon: <MailOutlined />,
    children: [
      { key: 'g1', label: 'Item 1', type: 'group', children: [
        { key: '1', label: 'Option 1' }, { key: '2', label: 'Option 2' },
      ] },
      // ...
    ],
  },
  // 也支持 { type: 'divider' } 分隔线与更多 group
];

<Menu
  onClick={onClick}
  style={{ width: 256 }}
  defaultSelectedKeys={['1']}
  defaultOpenKeys={['sub1']}
  mode="inline"
  items={items}
/>

inline 模式的特点:子菜单以内嵌展开/收起的方式呈现(不弹出浮层),支持任意层级嵌套;分组(type: 'group')与分隔线(type: 'divider')都能直接写进 itemsdefaultOpenKeysdefaultSelectedKeys 用于设置默认展开与默认选中。

折叠菜单、Tooltip 与展开图标

侧边栏折叠(inline collapsed)是管理后台的标配能力,参考 components/menu/demo/inline-collapsed.tsxcomponents/menu/demo/tooltip.tsx。折叠后菜单只展示图标,文字隐藏;鼠标悬停时通过 Tooltip 浮出完整文字:

const [collapsed, setCollapsed] = useState(false);
const [tooltipEnabled, setTooltipEnabled] = useState(true);

<Menu
  defaultSelectedKeys={['1']}
  defaultOpenKeys={['sub1']}
  mode="inline"
  theme="dark"
  inlineCollapsed={collapsed}
  tooltip={tooltipEnabled ? { placement: 'left' } : false}  // v6.3.0 起可用false 关闭
  items={items}
/>
  • inlineCollapsed:仅在 mode="inline" 时生效(源码 components/menu/menu.tsx 会在开发模式下对“非 inline 模式使用 inlineCollapsed”发出 usage 警告)。
  • tooltip:配置折叠状态下菜单项的 Tooltip 属性,置为 false 可完全关闭。该能力在 v6.3.0 引入。
  • 折叠时若菜单项没有 icon,会退化为展示文字首字符的占位节点,相关逻辑见 components/menu/MenuItem.tsxtitle 属性用于手动指定折叠项要展示的 Tooltip 标题,Menu 源码内部有一个对应的受控 open 状态以避免折叠动画过程中的闪烁。

此外还有“折叠后图标对齐”的调试示例 components/menu/demo/collapsed-icon-debug.tsx

垂直菜单与暗色主题

垂直弹出式菜单 mode="vertical" 的官方示例为 components/menu/demo/vertical.tsx;主题切换则见 components/menu/demo/theme.tsx

const [theme, setTheme] = useState<MenuTheme>('dark');

<Menu
  theme={theme}   // 'light' | 'dark',默认 'light'
  mode="inline"
  style={{ width: 256 }}
  defaultOpenKeys={['sub1']}
  selectedKeys={[current]}
  items={items}
/>

theme 默认继承 Menu 的全局主题,但 SubMenuType 上提供了独立的 theme 字段,可以让某个子菜单浮层使用与整体不同的主题(即 components/menu/demo/submenu-theme.tsx 演示的 Sub-menu theme)。此外:

自定义子菜单浮层:popupRender

对于顶部导航想实现“悬浮大面板”这种设计(如首页类站点的产品导航),v5.15.0 之后可以用 popupRender 完全接管子菜单浮层的渲染。官方示例 components/menu/demo/custom-popup-render.tsx 将每个子菜单渲染成一个带标题的卡片式浮层:

const popupRender: MenuProps['popupRender'] = (_, { item }) => {
  return (
    <Flex className={styles.navigationPopup} vertical gap="medium">
      <Typography.Title level={3}>{item.title}</Typography.Title>
      <Row gutter={16}>
        {React.Children.map(item.children as React.ReactNode, (child) =>
          React.isValidElement(child) ? (
            <Col span={12} key={child.key}>{child}</Col>
          ) : null,
        )}
      </Row>
    </Flex>
  );
};

<Menu mode="horizontal" items={menuItems} popupRender={popupRender} />

注意 popupRender 在 Menu 与单个 SubMenu 两级上都可配置(见下文 API 表),其签名统一为 (node: ReactElement, props: { item: SubMenuProps; keys: string[] }) => ReactNode。从源码看,该示例还通过在 ConfigProvider.theme.components.Menu 中注入 popupBghorizontalItemSelectedColorhorizontalItemHoverColor 等组件级 Token 来定制浮层与水平高亮颜色。

API 全解

提示:Menu 作为 items 数组项的元素类型,最终类型化定义见 components/menu/interface.ts;组件复合形态(Menu.ItemMenu.SubMenuMenu.DividerMenu.ItemGroup)的静态挂载见 components/menu/index.tsx

通用属性(如 classNamestyleid 等)参考 Common props(原文档入口为 /docs/react/common-props)。

Menu

Property Description Type Default Version Global Config
classNames Customize class for each semantic structure inside the component. Supports object or function. Record<SemanticDOM, string> | (info: { props }) => Record<SemanticDOM, string> - - 6.0.0
defaultOpenKeys Array with the keys of default opened sub menus string[] - - ×
defaultSelectedKeys Array with the keys of default selected menu items string[] - - ×
expandIcon custom expand icon of submenu ReactNode | (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode - 4.9.0 5.15.0
forceSubMenuRender Render submenu into DOM before it becomes visible boolean false - ×
inlineCollapsed Specifies the collapsed status when menu is inline mode boolean - - ×
inlineIndent Indent (in pixels) of inline menu items on each level number 24 - ×
items Menu item content ItemType[] - 4.20.0 ×
mode Type of menu vertical | horizontal | inline vertical - ×
multiple Allows selection of multiple items boolean false - ×
openKeys Array with the keys of currently opened sub-menus string[] - - ×
overflowedIndicator Customized the ellipsis icon when menu is collapsed horizontally ReactNode <EllipsisOutlined /> - ×
selectable Allows selecting menu items boolean true - ×
selectedKeys Array with the keys of currently selected menu items string[] - - ×
styles Customize inline style for each semantic structure inside the component. Supports object or function. Record<SemanticDOM, CSSProperties> | (info: { props }) => Record<SemanticDOM, CSSProperties> - - 6.0.0
subMenuCloseDelay Delay time to hide submenu when mouse leaves (in seconds) number 0.1 - ×
subMenuOpenDelay Delay time to show submenu when mouse enters, (in seconds) number 0 - ×
tooltip Config tooltip props for menu items in inline collapsed mode. Set to false to disable. false | TooltipProps - 6.3.0 ×
theme Color theme of the menu light | dark light - ×
triggerSubMenuAction Which action can trigger submenu open/close hover | click hover - ×
onClick Called when a menu item is clicked function({ key, keyPath, domEvent, itemData }) - - ×
onDeselect Called when a menu item is deselected (multiple mode only) function({ key, keyPath, selectedKeys, domEvent, itemData }) - - ×
onOpenChange Called when sub-menus are opened or closed function(openKeys: string[]) - - ×
onSelect Called when a menu item is selected function({ key, keyPath, selectedKeys, domEvent, itemData }) - - ×
popupRender Custom popup renderer for submenu (node: ReactElement, props: { item: SubMenuProps; keys: string[] }) => ReactNode - - ×

几个参数要点结合源码展开说明:

  • onClick / onSelect / onDeselect 回调信息:在选中相关回调中,key 为被选中项 key,keyPath 为从选中项到根的 key 路径数组,selectedKeys 为当前全部选中项;onDeselect 仅在 multiple 模式发生。
  • overflowedIndicator:水平模式下当菜单溢出容器宽度时,超出的项会收进一个“更多”浮层中,其触发图标默认是省略号 <EllipsisOutlined />,可自定义;该默认图标与浮层 class 在 components/menu/menu.tsx 中被注入到 rc-menu。
  • Global Config(全局配置):只有 classNames / styles(6.0.0 起)与 expandIcon(5.15.0 起)可以通过 ConfigProvider 的组件级配置在全局统一注入;其余属性都不参与全局配置。menu.tsx 中通过 useComponentConfig('menu') 读取 contextClassNames/contextStyles,再由 useMergeSemantic 合并到组件自身传入值。
  • 受控/非受控openKeysselectedKeys 提供受控模式,配合 defaultOpenKeysdefaultSelectedKeys 使用非受控模式。

ItemType 配置体系

items 的每一项都是如下联合类型(原文档原文):

type ItemType = MenuItemType | SubMenuType | MenuItemGroupType | MenuDividerType;

注意该项的 TypeScript 实现位于 components/menu/interface.ts,并且额外允许 null(便于条件渲染占位)。所有条目均支持任意 data-* 自定义属性透传到 DOM。

MenuItemType(普通菜单项)

Property Description Type Default Version
danger Display the danger style boolean false -
disabled Whether menu item is disabled boolean false -
extra The extra of the menu item ReactNode - 5.21.0
icon The icon of the menu item ReactNode - -
key Unique ID of the menu item string - -
label Menu label ReactNode - -
title Set display title for collapsed item string - -

SubMenuType(子菜单)

Property Description Type Default Version
children Sub-menus or sub-menu items ItemType[] - -
disabled Whether sub-menu is disabled boolean false -
icon Icon of sub menu ReactNode - -
key Unique ID of the sub-menu string - -
label Menu label ReactNode - -
popupClassName Sub-menu class name, not working when mode="inline" string - -
popupOffset Sub-menu offset, not working when mode="inline" [number, number] - -
theme Color theme of the SubMenu (inherits from Menu by default) light | dark - -
onTitleClick Callback executed when the sub-menu title is clicked function({ key, domEvent }) - -
popupRender Custom popup renderer for current sub-menu (node: ReactElement, props: { item: SubMenuProps; keys: string[] }) => ReactNode - -

需要留意的是:popupClassNamepopupOffsetpopupRender 这类“浮层”配置在 mode="inline" 下不生效,因为 inline 模式的子菜单是内嵌展开而非弹出浮层。SubMenu 组件实现见 components/menu/SubMenu.tsx:它会通过 MenuContext 携带第一层之外的信息,为浮层合并 prefixCls、主题类名(${prefixCls}-${theme})并通过 useZIndex 计算弹出层 zIndeximport { useZIndex } from '../_util/hooks')。

MenuItemGroupType(菜单分组)

将条目 type 定义为 group 即可把子项编组,例如:

const groupItem = {
  type: 'group', // Must have
  label: 'My Group',
  children: [],
};
Property Description Type Default Version
children Sub-menu items MenuItemType[] - -
label The title of the group ReactNode - -

注意源码 components/menu/interface.tsMenuItemGroupType 额外允许可选的 key 字段。

MenuDividerType(菜单分隔线)

分隔线仅用于垂直弹出式 Menu 或 Dropdown Menu 中,需要将 type 定义为 divider

const dividerItem = {
  type: 'divider', // Must have
};
Property Description Type Default Version
dashed Whether line is dashed boolean false -

补充:官方文档表示 rc-menu 层还提供更多底层配置(rc 相关源码在 node_modules 中,可自行查阅),antd 侧已在 components/menu/interface.ts 基于 rc 类型扩展出上述 dangericonextratitledashed 等业务字段。

进入源码:antd Menu 的分层与联动设计

文件结构与职责

components/menu 目录结构清晰反映了“外壳组件(面向用户)→ 内部实现(连接 rc-menu)→ 子组件(Item/SubMenu/Divider)→ 样式与上下文”的分层:

文件 职责
index.tsx 对外复合组件:静态挂载 Menu.Item / Menu.SubMenu / Menu.Divider / Menu.ItemGroup;通过 forwardRef 暴露 { menu, focus } 句柄
menu.tsx 核心实现 InternalMenu:props 归并、主题/折叠合并、Context 构建、动画注入、语义化 classNames/styles 合并
MenuItem.tsx 单项封装:折叠态文字/无 icon 首字符处理、折叠态 Tooltip 逻辑、danger/extra 样式类
SubMenu.tsx 子菜单封装:展开图标、主题继承、浮层 class/zIndex
MenuDivider.tsx 分隔线
interface.ts ItemType 系列类型定义
MenuContext.tsx 共享 prefixCls / theme / mode / inlineCollapsed / tooltip 等上下文
OverrideContext.tsx 被 Dropdown 等“菜单内嵌容器”场景覆盖 context 的机制
style 样式入口,拆分为 index/horizontal/vertical/rtl/theme 五份,并导出组件级 Design Token

InternalMenu 的核心归并逻辑

antd Menu 的入口是 menu.tsxforwardRef 实现的 InternalMenu,随后渲染到 @rc-component/menuRcMenu 上。关键归并点包括:

  • 模式、可选、折叠的优先级归并menu.tsx):mergedMode = overrideObj.mode || modemergedSelectable = selectable ?? overrideObj.selectablemergedInlineCollapsed = inlineCollapsed ?? siderCollapsed。其中 siderCollapsed 来自 Layout 的 SiderContext——这正是 Menu 放进 Sider 后能自动跟随折叠的底层原因。
  • 展开图标三级回退expandIcon):组件 props → OverrideContext → ConfigProvider 的 menu 组件配置 → 默认图标,见 menu.tsx
  • 开发期告警menu.tsx):
    • 在非 inline 模式下传 inlineCollapsed 会输出 usage 警告;
    • 同时传 childrenitems 时会提示“请用 items 替代 children”(deprecated 语义)。
  • 模式对应的动效defaultMotions):horizontal 使用 slide-up、inline 使用折叠展开动画、vertical 使用 zoom-big,见 menu.tsx
  • 语义 class/styles 合并:组件自身 classNames/styles 与 ConfigProvider 的组件级配置通过 useMergeSemantic 合并,并将 popup 映射到 rootsubMenu 映射到 item 的默认语义节点,见 menu.tsx

点击链路与 Dropdown 的协作

Menu 的点击经过 onItemClickuseEvent 包装)串联两层回调:既触发使用方传入的 onClick,也会调用 overrideObj.onClick?.() 通知外层(如 Dropdown 关闭自身),见 menu.tsx。结合 Layout 的 Sider、下拉菜单 Dropdown、以及 ConfigProvider,Menu 是 antd 中“复合导航体系”的中枢组件。

FAQ 中容易踩的坑

原文档 FAQ 一节回答了两个非常实际的问题。

为什么 Menu 的 children 会被渲染两次?

Menu 通过“两次渲染”(twice-render)技术来收集自身的节点结构,以支持 HOC 使用方式(例如将自定义逻辑包在 Menu.Item 外)。如果合并成一次渲染,收集逻辑会变得异常复杂。因此官方明确:

  • 不要因为“渲染两次”感到惊讶,这是有意为之的结构收集策略;
  • 官方欢迎社区帮助改进该收集逻辑。

这也是前文“Notes for developers”要求 children 必须是 Menu.* 或基于其封装的 HOC 的根本原因。

为什么 Menu 在 Flex 布局中不会响应式折叠?

在 Flex 布局中,Menu 会先完整渲染所有菜单项、再去做横向折叠。因此你需要告诉 Flex“不要考虑 Menu 的宽度”,才能让 overflowedIndicator 的横向折叠生效。官方给出的最小修复写法:

<div style={{ flex }}>
  <div style={{ ... }}>Some Content</div>
  <Menu style={{ minWidth: 0, flex: "auto" }} />
</div>

关键点在于给 Menu 设置 style={{ minWidth: 0, flex: 'auto' }},避免 Flex 子项按内容撑开、破坏横向溢出折叠的计算。

语义化 DOM(Semantic DOM)定制

antd v6 起,Menu 支持按“语义结构”批量定制 class 与样式。官方文档以 <code src="./demo/_semantic.tsx"> 动态渲染语义结构示意图,对应演示文件为 components/menu/demo/_semantic.tsx。从 menu.tsx 的类型定义可以确认 Menu 支持如下语义节点:

  • 顶级:rootlistitemTitleitemitemIconitemContent
  • 子菜单(classNames.subMenu.* / styles.subMenu.*):itemitemTitlelistitemContentitemIcon
  • 浮层(classNames.popup.root):root

classNamesstyles 均支持两种形态:

// 对象形态
classNames={{ root: 'my-menu', item: ({ ... }) => 'my-item' }}
styles={{ list: { maxHeight: 400 } }}

// 函数形态(可根据当前 props 决定返回值)
classNames={(info: { props: MenuProps }) => ({ root: info.props.theme === 'dark' ? 'dark-root' : '' })}

内部实现中,list / itemTitle 直接透传给 rc-menu 的 classNames / styles,其余节点在 MenuItem / SubMenu / Menu 根节点上各自拼接,对应源码见 menu.tsxMenuItem.tsx。6.0.0 起 classNames/styles 也支持通过 ConfigProvider 全局配置(参见 Global Config 列)。仓库测试 components/menu/tests/semantic.test.tsx 覆盖了该能力。

通过 Design Token 定制主题

Menu 的样式基于 antd 的 Design Token 体系构建。官方文档尾部通过 <ComponentTokenTable component="Menu"> 渲染出完整的组件级 Token 表,Token 定义从仓库结构上看集中在 components/menu/style/theme.ts(配合 style/index.tshorizontal.tsvertical.tsrtl.ts 按模式拆分实现)。

在业务代码中,你可以像官方 custom-popup-render.tsx 示例那样,通过 ConfigProvidertheme.components.Menu 覆盖任意组件 Token:

<ConfigProvider
  theme={{
    components: {
      Menu: {
        popupBg: '#fff',
        horizontalItemSelectedColor: '#1677ff',
        horizontalItemHoverColor: '#1677ff',
      },
    },
  }}
>
  <Menu mode="horizontal" items={menuItems} popupRender={popupRender} />
</ConfigProvider>

例如 popupBg 控制弹出子菜单背景、horizontalItemSelectedColorhorizontalItemHoverColor 控制水平模式项选中/悬停文字颜色,从而实现不写一行 CSS 的轻量主题定制。仓库内另有一份用于调试的组件 Token 示例 components/menu/demo/component-token.tsx

小结

回顾整篇内容可以提炼出 Ant Design Menu 的三层使用心法:

  1. 数据驱动配置:优先使用 items + ItemType 联合类型声明菜单结构,group/divider/submenu 通过 type 字段即可混排,天然具备 TypeScript 提示与完整 key 管理。
  2. 模式决定形态horizontal 顶部平铺、inline 侧边内嵌、vertical 弹出子菜单;折叠(inlineCollapsed/Sider)、展开控制(openKeys)、主题(theme)之间可以自由组合切换。
  3. 按需深度定制:样式诉求从小到大依次使用 组件级 Design Token → Semantic DOM 的 classNames/stylespopupRender 自定义浮层 → @rc-component/menu 底层 API。

若需要把 Menu 放进页面骨架中使用,建议继续阅读 Layout 布局组件文档 并结合 Sider 的联动实现理解 inlineCollapsed 与侧栏折叠的关系。

登录后查看全文
热门项目推荐
相关项目推荐