ant-design Layout 侧边两列式布局实战:可收起侧边导航的完整实现与源码解析
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/gh_mirrors/ant/ant-design
侧边两列式布局(Sider Layout)是 ant-design 中最常用的页面级布局方案之一:主导航固定于页面左侧,辅助菜单与内容区位于右侧工作区,页面横向空间有限时侧边导航可收起。本文以仓库中 components/layout/demo/side.md 演示为核心,结合 side.tsx 完整示例与 Sider.tsx、layout.tsx 源码实现,带你掌握侧边布局的搭建步骤、Sider 全部核心 API、收起/响应式/固定侧边栏等进阶能力,以及其底层的工作原理。
侧边布局的适用场景与设计取舍
原文档明确指出,侧边两列式布局的核心特征是主导航放左侧固定位置、辅助菜单放工作区顶部,内容区根据浏览器终端自适应。这种结构带来三个明确收益:
- 横向空间使用率高:导航垂直堆叠后不占横向宽度,内容区可随视口伸缩自适应;
- 层级扩展性强:一、二、三级导航项可以在侧边栏内流畅、有层次地展示(配合
Menu的inline模式与子菜单展开),导航项之间的关联性一目了然; - 可固定、定位高效:侧边导航可以固定(fixed),用户在操作与浏览中能快速定位和切换当前位置。
代价是:这类导航会牺牲一部分横向页面内容空间。因此文档给出的判断准则是——当页面横向空间有限时,侧边导航应该支持收起(collapse),把空间还给内容区。
完整示例:一个可收起的侧边两列布局
先看仓库中 side.tsx 提供的完整可运行示例,它演示了侧边两列布局的典型骨架:左侧Sider放导航菜单,右侧嵌套Layout依次放Header、Content(含面包屑)、Footer:
import React, { useState } from 'react'; import { DesktopOutlined, FileOutlined, PieChartOutlined, TeamOutlined, UserOutlined, } from '@ant-design/icons'; import type { MenuProps } from 'antd'; import { Breadcrumb, Layout, Menu, theme } from 'antd'; const { Header, Content, Footer, Sider } = Layout; type MenuItem = Required<MenuProps>['items'][number]; function getItem( label: React.ReactNode, key: React.Key, icon?: React.ReactNode, children?: MenuItem[], ): MenuItem { return { key, icon, children, label, } as MenuItem; } const items: MenuItem[] = [ getItem('Option 1', '1', <PieChartOutlined />), getItem('Option 2', '2', <DesktopOutlined />), getItem('User', 'sub1', <UserOutlined />, [ getItem('Tom', '3'), getItem('Bill', '4'), getItem('Alex', '5'), ]), getItem('Team', 'sub2', <TeamOutlined />, [getItem('Team 1', '6'), getItem('Team 2', '8')]), getItem('Files', '9', <FileOutlined />), ]; const App: React.FC = () => { const [collapsed, setCollapsed] = useState(false); const { token: { colorBgContainer, borderRadiusLG }, } = theme.useToken(); return ( <Layout style={{ minHeight: '100vh' }}> <Sider collapsible collapsed={collapsed} onCollapse={(value) => setCollapsed(value)}> <div className="demo-logo-vertical" /> <Menu theme="dark" defaultSelectedKeys={['1']} mode="inline" items={items} /> </Sider> <Layout> <Header style={{ padding: 0, background: colorBgContainer }} /> <Content style={{ margin: '0 16px' }}> <Breadcrumb style={{ margin: '16px 0' }}> <Breadcrumb.Item>User</Breadcrumb.Item> <Breadcrumb.Item>Bill</Breadcrumb.Item> </Breadcrumb> <div style={{ padding: 24, minHeight: 360, background: colorBgContainer, borderRadius: borderRadiusLG, }} > Bill is a cat. </div> </Content> <Footer style={{ textAlign: 'center' }}> Ant Design ©{new Date().getFullYear()} Created by Ant UED </Footer> </Layout> </Layout> ); }; export default App;骨架逐段拆解
1. 最外层Layout:撑满视口并激活侧边栏感知
<Layout style={{ minHeight: '100vh' }}>外层Layout是页面级容器。给minHeight: '100vh'让整个布局至少占满一屏高度。从 layout.tsx 源码可以看到,Layout内部通过useHasSider(实现见 hooks/useHasSider.ts)检测子元素中是否存在Sider:
- 优先使用显式传入的
hasSider布尔值; - 否则读取注册进
LayoutContext的 sider 列表(Sider挂载时会调用siderHook.addSider注册自身 id,卸载时removeSider); - 兜底方案是直接扫描
children,判断是否有node.type === Sider。
一旦判定存在 Sider,Layout会加上ant-layout-has-sider样式类,从而开启flex布局,让侧边栏与内容区并排排列。这也解释了为什么文档要求Sider"只能放在Layout中"——它依赖这个上下文注册机制。
2.Sider:可收起的左侧导航
<Sider collapsible collapsed={collapsed} onCollapse={(value) => setCollapsed(value)}>这是本示例的核心,三个属性共同完成"可收起":
collapsible:开启收起功能,Sider底部会自动渲染一个 trigger 触发器;collapsed:受控的收起状态,由 React state 管理;onCollapse:收起/展开时的回调,把最新状态写回 state,实现受控循环。
Sider内部渲染的是语义化的<aside>标签(见 Sider.tsx),宽度通过flex: 0 0 width、width、maxWidth、minWidth四个 CSS 属性同时约束,保证收起/展开时宽度严格等于目标值。默认展开宽200,收起宽80;点击 trigger 时toggle会调用handleSetCollapsed(!collapsed, 'clickTrigger')——注意受控模式下它只触发回调、不内部改 state,这正是示例用useState承接状态的原因。
侧边栏内部放了一个 Logo 占位<div className="demo-logo-vertical" />和Menu:
<Menu theme="dark" defaultSelectedKeys={['1']} mode="inline" items={items} />Menu的mode="inline"让子菜单以垂直内联方式展开,正是文档所说"一、二、三级导航项顺畅且有层次地展示"的实现方式;items中User、Team两个带children的项就是二级导航。theme="dark"与Sider默认的dark主题保持一致。
3. 右侧Layout:头部、内容与页脚
<Layout> <Header style={{ padding: 0, background: colorBgContainer }} /> <Content style={{ margin: '0 16px' }}> <Breadcrumb>...</Breadcrumb> ... </Content> <Footer>...</Footer> </Layout>右侧再嵌套一个Layout,内部垂直排列Header、Content、Footer,这就是文档所说的"辅助菜单放置于工作区顶部"的载体。示例从theme.useToken()取colorBgContainer与borderRadiusLG,让 Header 和内容卡片背景跟随主题 Token,属于 antd 5.x 推荐的做法。
Sider 核心 API 全览
结合 index.zh-CN.md 的 API 表格与 Sider.tsx 的默认值实现,侧边布局常用的Layout.Sider参数如下:
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| collapsible | 是否可收起 | boolean | false |
| collapsed | 当前收起状态(受控) | boolean | - |
| defaultCollapsed | 是否默认收起 | boolean | false |
| width | 侧边栏宽度 | number | string | 200 |
| collapsedWidth | 收缩宽度,设置为 0 会出现特殊 trigger | number | 80 |
| breakpoint | 触发响应式布局的断点 | xs|sm|md|lg|xl|xxl | - |
| theme | 主题颜色 | light|dark | dark |
| trigger | 自定义 trigger,设置为 null 时隐藏 | ReactNode | - |
| reverseArrow | 翻转折叠提示箭头方向(Sider 在右边时用) | boolean | false |
| zeroWidthTriggerStyle | collapsedWidth为 0 时特殊 trigger 的样式 | object | - |
| onCollapse | 展开-收起回调,由点击 trigger 或响应式反馈两种方式触发 | (collapsed, type) => {} | - |
| onBreakpoint | 触发响应式断点时的回调 | (broken) => {} | - |
| className / style | 容器类名 / 样式 | string / CSSProperties | - |
几点源码级的细节值得注意:
- 宽度单位处理:源码中
rawWidth = collapsed ? collapsedWidth : width,随后用isNumeric判断:纯数字自动补px,字符串按原样传入,因此width既支持数字也支持'240px'这类字符串; - 受控与非受控:
'collapsed' in props决定 Sider 是受控还是非受控。非受控时内部 state 由defaultCollapsed初始化,点击 trigger 内部自动切换; - 零宽度特殊 trigger:当
collapsedWidth为 0 时,Sider 收起后完全不占空间,此时会出现一个浮动的 Bars 图标触发器(zero-width-trigger),点击可重新展开,zeroWidthTriggerStyle正是用来定制它的样式; - onCollapse 的 type 参数:
CollapseType只有两种取值——'clickTrigger'(点击触发器)和'responsive'(响应式断点触发),可用于区分收起原因; - RTL 支持:
reverseArrow用于翻转箭头,典型场景是 Sider 放在页面右侧时。
响应式侧边栏:横向空间不足时自动收起
文档强调"页面横向空间有限时,侧边导航可收起",除了手动点击 trigger,ant-design 还提供了基于断点的自动收起能力。参考 responsive.tsx:
<Sider breakpoint="lg" collapsedWidth="0" onBreakpoint={(broken) => { console.log(broken); }} onCollapse={(collapsed, type) => { console.log(collapsed, type); }} >breakpoint="lg"表示视口宽度低于992px时侧边栏自动收起。源码中dimensionMaxMap定义了各断点的媒体查询上限(见 Sider.tsx,lg: '991.98px'),Sider挂载时通过window.matchMedia注册监听,断点命中时同时触发onBreakpoint(broken)与onCollapse(collapsed, 'responsive'),并把内部 collapsed 状态同步为断点状态。此示例把collapsedWidth设为"0",配合零宽度 trigger,让侧边栏在窄屏下完全隐藏、最大化内容空间。
各断点对应的宽度(即 index.zh-CN.md 中的 breakpoint width 表):
{ xs: '480px', sm: '576px', md: '768px', lg: '992px', xl: '1200px', xxl: '1600px', }固定侧边栏:滚动内容、导航始终可见
文档提到"侧边导航可以固定,使得用户在操作和浏览中可以快速的定位和切换当前位置"。参考 fixed-sider.tsx 的实现思路——给Sider施加position: fixed样式,并让内容区左侧预留等宽 margin:
const siderStyle: React.CSSProperties = { overflow: 'auto', height: '100vh', position: 'fixed', insetInlineStart: 0, top: 0, bottom: 0, scrollbarWidth: 'thin', scrollbarColor: 'unset', }; <Layout hasSider> <Sider style={siderStyle}>...</Sider> <Layout style={{ marginInlineStart: 200 }}> {/* 内容区自行滚动 */} </Layout> </Layout>要点有三:
- 外层
Layout显式传hasSider,保证在服务端渲染或Sider样式检测不到时也能稳定得到has-sider布局(官方文档说明hasSider一般不用指定,主要供 SSR 场景避免样式闪动); - 侧边栏自身
position: fixed+height: 100vh钉住视口,内容长时只在侧边栏内部滚动(overflow: auto); - 右侧内容区用
marginInlineStart: 200预留与 Sider 展开宽度一致的间距(使用逻辑属性,天然兼容 RTL)。
侧边导航的设计规则速览
从 index.zh-CN.md 的"设计规则"一节可以提炼出与侧边布局强相关的规范,作为页面落地的参考:
- 尺寸:侧边导航宽度的范围计算公式为
200+8n(n为自然数),即 200、208、216……可按信息层级取整数值;顶部一级导航高64px、二级48px; - 交互:当前导航项在呈现上优先级最高;导航收起时,当前项的选中样式自动赋予其上一层级;左侧导航的收放同时支持手风琴与全展开两种交互模式;
- 视觉:深色底用"大色块强调"父级导航,浅色底用"高亮火柴棍"标识当前项,上一级可用字体高亮变色;导航标准字号为
12px/14px,其中 14px 用于一、二级导航。
常见问题与注意事项
- Sider 必须放在
Layout内:Sider依赖Layout提供的LayoutContext(siderHook)完成注册,脱离Layout将无法参与has-sider布局判定; - 受控 collapsed 需要自管状态:传入
collapsed后 Sider 不再内部改状态,必须通过onCollapse回调同步外部 state,否则点击 trigger 无法收起(示例中useState即为此); - 宽度的浏览器兼容:
Layout采用 flex 布局实现,请留意目标浏览器的 flex 兼容性(见 index.zh-CN.md 组件概述下方的提示); - 固定侧边栏要补偿占位:
position: fixed使 Sider 脱离文档流,需手动给内容区加marginInlineStart,宽度与 Sider 展开宽度保持一致,否则内容会被遮挡; - 想快速获得完整后台骨架:官方文档在 side.md 中提示,需要 3 分钟快速搭建时可参考 ProLayout(Pro 组件体系的布局方案),适合需要现成中后台框架的场景。
小结
侧边两列式布局以"左侧主导航 + 右侧工作区"的结构,在横向空间利用、导航层级扩展与定位效率之间取得了平衡,代价是占用一部分横向内容空间。通过 side.tsx 的collapsible+ 受控collapsed组合可以随时把空间还给内容;配合breakpoint断点自动收起、collapsedWidth={0}零宽模式,以及position: fixed固定侧边栏,即可覆盖中后台页面的绝大多数布局诉求。理解了 Sider.tsx 中宽度计算、trigger 渲染、响应式监听与 layout.tsx 的has-sider判定机制后,你也能在定制侧边布局时做到心中有数。
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/gh_mirrors/ant/ant-design
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考