第七章 第二节:Ant Design 导航篇 —— 解析导航组件设计思路,掌握 Menu/Breadcrumb 实战应用技巧
7.6. 指引方向:实战导航(Navigation)组件
在 7.5
节中,我们搭建了专业的页面“骨架”。您可能还记得,在 AdminLayout
的侧边栏里,我们用 <Menu />
组件构建了一个菜单,但对它的配置和用法并未深究。
一个优秀的应用,不仅要有稳固的骨架,更要有清晰的“路标”来指引用户。本节,我们将聚焦于 Ant Design 的核心导航组件,学会如何为用户提供从应用宏观跳转到页面微观定位的全方位指引。
本节目标:我们将模拟一个常见的后台管理页面——“文章列表页”,并在这个页面中,综合运用 Breadcrumb
, Menu
, Dropdown
和 Anchor
四个核心导航组件,构建一个功能完整、流线清晰的用户界面。
项目结构预览:
1 2 3 4 5 6
| ├── components/ │ └── demos/ │ └── └── pages/ └── ArticleListPage.tsx
|
第一步:页面上下文 - Breadcrumb
(面包屑)
面包屑是告知用户“我在哪里”的最直观方式,对于层级较深的应用至关重要。
目标:在我们的新页面顶部,添加一个面包屑导航,清晰地标示出当前页面所处的层级。
文件路径: src/pages/ArticleListPage.tsx
(初始内容)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import React from 'react'; import { Breadcrumb, Typography } from 'antd'; import { HomeOutlined, FileTextOutlined } from '@ant--design/icons';
const { Title } = Typography;
const ArticleListPage: React.FC = () => { return ( <div> {/* 1. Breadcrumb 用于展示页面层级 */} {/* 现代 antd 推荐使用 `items` 属性来数据驱动地渲染面包屑 */} <Breadcrumb items={[ { href: '/', title: <HomeOutlined />, // 可以内嵌 Icon }, { title: '文章管理', }, { title: '文章列表', }, ]} /> <Title level={2} style={{ marginTop: '16px' }}> 文章列表 </Title> </div> ); };
export default ArticleListPage;
|
核心知识点:
items
属性: 这是 Breadcrumb
组件最推荐的使用方式。通过一个对象数组来定义导航路径,每个对象可以包含 title
, href
, onClick
等属性,甚至可以通过 menu
属性创建带下拉菜单的面包屑项。这种数据驱动的方式让面包屑的管理和动态生成变得非常简单。
一个页面通常包含多种操作。Dropdown
用于收纳一组折叠的操作命令,而水平模式的 Menu
则常用于页面内的分类切换。
目标:在页面标题下方,构建一个操作栏。左侧是“新建文章”等主要操作,其中一个操作通过 Dropdown
展示更多选项;右侧则是一个用于筛选文章状态的水平 Menu
。
文件路径: src/pages/ArticleListPage.tsx
(添加操作栏)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| import React from "react"; import { Breadcrumb, Typography, Button, Dropdown, Menu, Flex, Divider, } from "antd"; import type { MenuProps } from "antd"; import { HomeOutlined, PlusOutlined, DownOutlined, SettingOutlined, CheckCircleOutlined, SyncOutlined, CloseCircleOutlined, } from "@ant-design/icons";
const { Title } = Typography;
const items: MenuProps["items"] = [ { key: "1", label: "导出为 PDF" }, { key: "2", label: "导出为 Excel" }, { type: "divider" }, { key: "3", label: "从模板导入", danger: true }, ];
const MenuItems: MenuProps["items"] = [ { key: "all", label: "全部文章" }, { key: "published", label: "已发布", icon: <CheckCircleOutlined /> }, { key: "draft", label: "草稿箱", icon: <SyncOutlined /> }, { key: "trashed", label: "回收站", icon: <CloseCircleOutlined />, danger: true, }, ]; const ArticleListPage: React.FC = () => { return ( <div> {/* 1. Breadcrumb 用于展示页面层级 */} {/* 现代 antd 推荐使用 `items` 属性来数据驱动地渲染面包屑 */} <Breadcrumb items={[ { href: "/", title: <HomeOutlined />, // 可以内嵌 Icon }, { title: "文章管理", }, { title: "文章列表", }, ]} />
<Flex justify="space-between" align="center" style={{ marginTop: "16px" }} > <Title level={2} style={{ margin: 0 }}> 文章列表 </Title> <Flex gap="small"> <Button type="primary" icon={<PlusOutlined />}> 新建文章 </Button> {/* Dropdown 用于收纳一组操作 */} <Dropdown menu={{ items }}> <Button> 更多操作 <DownOutlined /> </Button> </Dropdown> </Flex> </Flex>
<Divider /> {/* 3. 水平 Menu 用于分类筛选 */} <Menu mode="horizontal" items={MenuItems} defaultSelectedKeys={["all"]} /> </div> ); };
export default ArticleListPage;
|
核心知识点:
Dropdown
: 它的核心是由一个 触发元素(这里是 <Button>
)和一个通过 menu
属性定义的 菜单列表 组成。menu
属性接收一个与 Menu
组件的 items
结构完全相同的对象,实现了组件之间的高度复用。Menu
的 mode
属性: 通过将 mode
设置为 'horizontal'
,Menu
组件即可从垂直布局切换为水平布局,非常适合用作顶栏导航或页内标签式导航。
最后,我们将这个新建的页面组件放入我们的 AdminLayout
中。
文件路径: src/components/demos/AdminLayout.tsx
(修改 Content
部分)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import ArticleListPage from '../../pages/ArticleListPage';
const AdminLayout: React.FC = () => { return ( <Layout style={{ minHeight: '100vh' }}> <Sider /* ... */ /> <Layout> <Header /* ... */ /> {/* 👇 将 Content 内部替换为我们的新页面 */} <Content style={{ margin: '0 16px', paddingTop: '16px' }}> <ArticleListPage /> </Content> <Footer /* ... */ /> </Layout> </Layout> ); }
export default AdminLayout;
|
通过构建一个真实的“文章管理”页面,我们一站式地掌握了 Ant Design 四大核心导航组件的实战用法,并理解了它们在应用中所扮演的不同角色:
Breadcrumb
: 我在哪? —— 提供清晰的层级定位。Menu
: 我能去哪? —— 提供系统性的、可供选择的跳转路径(垂直或水平)。Dropdown
: 我能做什么? —— 收纳当前上下文中的一组操作命令。
在本节中,我们将采用全新的“原子化 Demo”模式,为两个功能独特但非常实用的组件——Anchor
(锚点) 和 FloatButton
(悬浮按钮)——创建独立的、聚焦的实战示例。
7.7.1. Anchor
:长页面的“任意门”
当一个页面承载了大量内容,需要用户频繁滚动时,一个清晰的页面内导航(锚点)就显得至关重要。
目标:创建一个独立的、可滚动的页面,并使用 Anchor
组件为其提供一个固定的、可交互的目录。
文件路径: src/components/demos/AnchorDemoPage.tsx

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| import React from "react"; import { Row, Col, Anchor, Typography, Divider } from "antd";
const { Title, Paragraph } = Typography; const AnchorDemoPage = () => { const renderContent = (title: string, count: number) => { return ( <div> <Title level={3} id={title.toLowerCase().replace(/\s/g, "-")}> {title} </Title> {Array.from({ length: count }, (_, i) => ( <Paragraph key={i}> 这是关于“{title}”的第 {i + 1} 段详细内容。Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo. </Paragraph> ))} </div> ); };
return ( <div> <Row gutter={24}> {/* 左侧:可滚动的内容区 */} <Col span={18}> <Typography> {renderContent("基本介绍", 5)} <Divider /> {renderContent("安装与配置", 8)} <Divider /> {renderContent("API 详解", 10)} <Divider /> {renderContent("常见问题 FAQ", 6)} </Typography> </Col>
{/* 右侧:锚点导航 */} <Col span={6}> {/* Anchor 默认是 affix (固定) 模式,会自动固定在屏幕上。 `targetOffset` 属性非常有用,可以设置一个偏移量, 避免跳转后的标题被顶部的固定导航栏遮挡。 */} <Anchor targetOffset={80}> <Anchor.Link href="#基本介绍" title="基本介绍" /> <Anchor.Link href="#安装与配置" title="安装与配置" /> <Anchor.Link href="#api-详解" title="API 详解" /> <Anchor.Link href="#常见问题-faq" title="常见问题 FAQ" /> </Anchor> </Col> </Row> </div> ); };
export default AnchorDemoPage;
|
在 App.tsx
中独立使用此 Demo:
文件路径: src/App.tsx
1 2 3 4 5 6 7 8 9 10 11
| import React from 'react'; import AnchorDemoPage from './components/demos/AnchorDemoPage';
const App: React.FC = () => { return ( <AnchorDemoPage /> ); };
export default App;
|
本 Demo 核心知识点:
- 关联
id
与 href
: Anchor
的工作原理是通过 items
数组中每个对象的 href
属性(如 '#基本介绍'
)来寻找页面上具有相同 id
的元素(如 <Title id="基本介绍">
),并滚动到该位置。 targetOffset
: 这是一个在实战中几乎必配的属性。现代网页大多有固定的顶部导航栏,若无偏移,锚点跳转后,标题会被导航栏遮住。targetOffset
就是为了解决这个问题而存在的。- 数据驱动: 再次强调,使用
items
属性来定义导航结构,是 Ant Design 现代、推荐的做法,它让动态生成 Anchor
变得轻而易举。
FloatButton
提供了一种不打断用户当前浏览流,但又始终可用的全局操作入口。除了单个按钮,它还支持更强大的“按钮组”和“回到顶部”功能。
目标:在一个可滚动的内容区域,展示 FloatButton
的多种形态:带徽标的通知按钮、可展开的菜单式按钮组、以及智能的“回到顶部”按钮。

文件路径: src/components/demos/FloatButtonDemo.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import React from 'react'; import { FloatButton, Typography, Divider, Badge } from 'antd'; import { CustomerServiceOutlined, CommentOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
const { Title, Paragraph } = Typography;
const FloatButtonDemo: React.FC = () => { return ( <div style={{ height: '300vh', padding: '24px', position: 'relative' }}> <Title>悬浮按钮功能展示</Title> <Paragraph>请向下滚动页面,以查看右下角的悬浮按钮效果。</Paragraph> <Divider />
{/* 1. 单个悬浮按钮:可以带 Badge 徽标 */} <Badge count={5}> <FloatButton icon={<CommentOutlined />} tooltip="查看消息" /> </Badge>
{/* 2. 菜单式按钮组:通过 trigger='click' 或 'hover' 触发 */} <FloatButton.Group trigger="click" type="primary" style={{ right: 94 }} // 调整位置以避免重叠 icon={<CustomerServiceOutlined />} tooltip="联系客服" > <FloatButton icon={<QuestionCircleOutlined />} /> <FloatButton icon={<SyncOutlined />} /> </FloatButton.Group>
{/* 3. 回到顶部:一个功能强大的专用悬浮按钮 */} {/* `visibilityHeight` 控制滚动多少像素后按钮才出现 */} <FloatButton.BackTop visibilityHeight={300} tooltip="回到顶部" /> </div> ); };
export default FloatButtonDemo;
|
在 App.tsx
中独立使用此 Demo:
文件路径: src/App.tsx
1 2 3 4 5 6 7 8 9 10
| import React from 'react'; import FloatButtonDemo from './components/demos/FloatButtonDemo';
const App: React.FC = () => { return ( <FloatButtonDemo /> ); };
export default App;
|
本 Demo 核心知识点:
FloatButton.Group
: 按钮组是 FloatButton 的一大特色。通过 trigger
属性可以将其变为一个可展开收起的“菜单”,极大地节省了屏幕空间。FloatButton.BackTop
: 内置了完整的“回到顶部”逻辑,我们只需将它放置在页面上,无需手动监听滚动事件或编写滚动动画。visibilityHeight
是其最常用的配置,用于智能地控制按钮的显隐。- 组合性:
FloatButton
可以与 Badge
(徽标数) 等组件无缝组合,实现更丰富的视觉提示效果。
7.8. 流程与内容分层:Steps
与 Tabs
在复杂的业务场景中,清晰地引导用户流程、合理地组织内容层次,是提升用户体验的关键。本节我们将学习两个专门用于此目的的组件:Steps
(步骤条) 用于线性流程引导,Tabs
(标签页) 用于非线性内容分层。
7.8.1. Steps
:将复杂任务拆解为清晰流程
当一个操作需要多个步骤才能完成时(如注册、申请、购买流程),使用 Steps
组件可以给用户一个清晰的“路线图”,让他们明确自己“当前在哪一步”以及“总共有几步”,从而降低用户的操作焦虑。
目标:创建一个独立的、交互式的多步骤注册流程。用户可以通过点击按钮在不同步骤间切换,并能直观地看到当前进度和状态。

文件路径: src/components/demos/MultiStepRegistration.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| import React, { useState } from 'react'; import { Steps, Button, Form, Input, Result, Typography, message, Divider } from 'antd'; import { UserOutlined, SolutionOutlined, CheckCircleOutlined, SmileOutlined } from '@ant-design/icons';
const { Title } = Typography;
const Step1Content = () => ( <Form layout="vertical"> <Form.Item label="用户名" required> <Input placeholder="请输入您的用户名" prefix={<UserOutlined />} /> </Form.Item> <Form.Item label="密码" required> <Input.Password placeholder="请输入您的密码" /> </Form.Item> </Form> );
const Step2Content = () => ( <Form layout="vertical"> <Form.Item label="真实姓名" required> <Input placeholder="请输入您的真实姓名" prefix={<SolutionOutlined />} /> </Form.Item> <Form.Item label="身份证号" required> <Input placeholder="请输入您的身份证号" /> </Form.Item> </Form> );
const Step3Content = () => ( <Result icon={<CheckCircleOutlined />} title="信息确认" subTitle="请仔细核对您填写的信息,确认无误后即可完成注册。" /> );
const MultiStepRegistration: React.FC = () => { const [current, setCurrent] = useState(0);
const steps = [ { title: '账户信息', description: '设置用户名和密码', icon: <UserOutlined />, content: <Step1Content />, }, { title: '实名认证', description: '填写您的真实信息', icon: <SolutionOutlined />, content: <Step2Content />, }, { title: '完成注册', description: '确认信息并提交', icon: <CheckCircleOutlined />, content: <Step3Content />, }, ];
const next = () => setCurrent(current + 1); const prev = () => setCurrent(current - 1); const onFinish = () => { message.success('恭喜您,注册成功!'); };
return ( <div style={{ padding: '24px', background: '#fff', borderRadius: '8px' }}> <Title level={3} style={{ textAlign: 'center' }}>新用户注册流程</Title> <Divider /> {/* 步骤条主体 */} <Steps current={current} items={steps.map(item => ({ key: item.title, title: item.title, description: item.description, icon: item.icon }))} /> {/* 内容展示区 */} <div style={{ marginTop: '24px', padding: '24px', border: '1px dashed #e9e9e9', borderRadius: '8px' }}> {steps[current].content} </div>
{/* 操作按钮区 */} <div style={{ marginTop: '24px', textAlign: 'center' }}> {current > 0 && <Button style={{ margin: '0 8px' }} onClick={prev}>上一步</Button>} {current < steps.length - 1 && <Button type="primary" onClick={next}>下一步</Button>} {current === steps.length - 1 && <Button type="primary" onClick={onFinish}>完成</Button>} </div> </div> ); };
export default MultiStepRegistration;
|
在 App.tsx
中独立使用此 Demo:
文件路径: src/App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React from 'react'; import MultiStepRegistration from './components/demos/MultiStepRegistration'; import { Flex } from 'antd';
const App: React.FC = () => { return ( <Flex align="center" justify="center" style={{ minHeight: '100vh', background: '#f0f2f5' }}> <div style={{ width: '600px' }}> <MultiStepRegistration /> </div> </Flex> ); };
export default App;
|
本 Demo 核心知识点:
- 受控模式:
Steps
组件的核心用法是 受控模式。我们通过 useState
维护一个 current
状态,并将其传递给 Steps
的 current
prop。当用户点击“下一步”或“上一步”时,我们更新这个 current
状态,Steps
组件的 UI 就会自动响应变化。 - 数据驱动
items
: 与其他导航组件一样,通过 items
属性来定义每一个步骤的内容(title
, description
, icon
, status
等)是最佳实践。 - 内容与步骤分离: 步骤条本身只负责展示流程状态,而每一步对应的具体内容(表单、结果等)应该在步骤条外部根据
current
状态来条件渲染。这是一种清晰的关注点分离模式。
🤔 思考与探索:如何实现“点状步骤条”与“可点击切换”?
上面的 Demo 已经非常实用了。但在某些场景下,我们可能需要更精简的视觉或更灵活的交互。
问题:
- 如果步骤条只是用来展示进度,而不需要详细的标题和描述,我们如何将其变为一个更简洁的 点状步骤条?
- 如果业务允许用户自由跳转到已经完成的步骤,我们如何实现 可点击的步骤条?
答案就在 Steps
组件的另外两个常用属性中。
1. 点状步骤条 (progressDot
)
当 Steps
与 Form
结合使用时,点状步骤条能提供更紧凑的布局。只需添加 progressDot
属性即可。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <Steps current={current} progressDot items={...} />
<Steps current={current} progressDot={(dot, { status, index }) => ( <Popover content={<span>Step {index} status: {status}</span>}> {dot} </Popover> )} items={...} />
|
2. 可点击切换 (onChange
)
Steps
组件提供了一个 onChange
回调函数。当用户点击某个步骤时,该函数会被触发,并返回被点击步骤的索引。我们可以利用这个函数来更新 current
状态,从而实现步骤的跳转。
1 2 3 4 5 6 7 8 9
| const onChange = (value: number) => { console.log('onChange:', value); if (value < current) { setCurrent(value); } };
<Steps current={current} onChange={onChange} items={...} />
|
掌握了这两个属性,您就可以根据不同的业务需求,灵活地定制 Steps
组件的外观和行为了。
7.8.2. Tabs
:在有限空间内组织多维内容
在我们学会了用 Steps
来引导 线性流程 后,现在我们来解决另一个常见问题:如何在同一个页面空间内,组织和展示 非线性的、平级的内容?Tabs
(标签页) 组件就是为此而生的完美解决方案。
第一部分:基础用法 - 内容区域切换
Tabs
的核心功能是在多个内容面板之间进行切换,保持界面整洁。
目标:创建一个独立的用户中心页面,使用 Tabs
来分别展示“个人资料”、“账户安全”和“消息通知”三个面板。我们将在这个 Demo 中探索 Tabs
的不同布局和附加功能。

文件路径: src/components/demos/UserSettingsTabs.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| import React, { useState } from 'react'; import { Tabs, Radio, Button, Typography, Space } from 'antd'; import type { RadioChangeEvent, TabsProps } from 'antd'; import { UserOutlined, SafetyCertificateOutlined, BellOutlined, SaveOutlined } from '@ant-design/icons';
const { Paragraph } = Typography; type TabPosition = 'left' | 'right' | 'top' | 'bottom';
const UserSettingsTabs: React.FC = () => { const [tabPosition, setTabPosition] = useState<TabPosition>('top');
const changeTabPosition = (e: RadioChangeEvent) => { setTabPosition(e.target.value); };
const items: TabsProps['items'] = [ { key: '1', label: '个人资料', icon: <UserOutlined />, children: <Paragraph>这里是个人资料的表单和内容。</Paragraph>, }, { key: '2', label: '账户安全', icon: <SafetyCertificateOutlined />, children: <Paragraph>这里是修改密码、绑定手机等安全设置。</Paragraph>, disabled: true, }, { key: '3', label: '消息通知', icon: <BellOutlined />, children: <Paragraph>这里是各类消息通知的开关设置。</Paragraph>, }, ];
return ( <div style={{ padding: '24px', background: '#fff', borderRadius: '8px' }}> <Space direction="vertical" style={{ width: '100%' }}> <div> <span style={{ marginRight: '16px' }}>标签页位置:</span> <Radio.Group value={tabPosition} onChange={changeTabPosition}> <Radio.Button value="top">Top</Radio.Button> <Radio.Button value="bottom">Bottom</Radio.Button> <Radio.Button value="left">Left</Radio.Button> <Radio.Button value="right">Right</Radio.Button> </Radio.Group> </div>
<Tabs tabPosition={tabPosition} // 2. 动态控制标签页的位置 defaultActiveKey="1" items={items} // 3. `tabBarExtraContent` 允许在标签栏添加额外元素,非常实用 tabBarExtraContent={<Button icon={<SaveOutlined />}>保存设置</Button>} /> </Space> </div> ); };
export default UserSettingsTabs;
|
在 App.tsx
中独立使用此 Demo:
文件路径: src/App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React from 'react'; import UserSettingsTabs from './components/demos/UserSettingsTabs'; import { Flex } from 'antd';
const App: React.FC = () => { return ( <Flex align="center" justify="center" style={{ minHeight: '100vh', background: '#f0f2f5' }}> <div style={{ width: '800px' }}> <UserSettingsTabs /> </div> </Flex> ); };
export default App;
|
本 Demo 核心知识点:
tabPosition
: 控制标签页的方位,支持 top
, bottom
, left
, right
四个方向,可以轻松实现垂直标签页布局。tabBarExtraContent
: 这是一个非常实用的属性,它允许我们在标签栏的右侧(或左侧)添加自定义的操作按钮或其他元素,而无需修改组件的内部结构。items
的 disabled
属性: 可以方便地禁用某一个标签页,使其不可点击。
第二部分:进阶用法 - 可增减的“卡片式”标签页
在某些应用中,例如代码编辑器、多文档浏览器,我们需要允许用户动态地添加和关闭标签页。Ant Design 的 type="editable-card"
模式就是为此而生。
目标:创建一个简易的多文档编辑器界面,用户可以自由添加新文档(标签页),也可以关闭任何一个已打开的文档。
文件路径: src/components/demos/EditableTabsEditor.tsx

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| import React, { useState, useRef } from "react"; import { Tabs, Button } from "antd"; import type { TabsProps } from "antd";
const EditableTabsEditor: React.FC = () => { const newTabIndex = useRef(0); const initialItems = [ { label: "文档 1", children: "这是文档 1 的内容", key: "1" }, { label: "文档 2", children: "这是文档 2 的内容", key: "2" }, ];
const [activeKey, setActiveKey] = useState(initialItems[0].key); const [items, setItems] = useState(initialItems);
const onChange = (newActiveKey: string) => { setActiveKey(newActiveKey); };
const onEdit = ( targetKey: React.MouseEvent | React.KeyboardEvent | string, action: "add" | "remove" ) => { if (action === "add") { add(); } else { remove(targetKey as string); } };
const add = () => { const newActiveKey = `newTab${newTabIndex.current++}`; const newPanes = [...items]; newPanes.push({ label: `新文档 ${newTabIndex.current}`, children: `这是新文档 ${newTabIndex.current} 的内容`, key: newActiveKey, }); setItems(newPanes); setActiveKey(newActiveKey); }; const remove = (targetKey: string) => { const newPanes = items.filter((item) => item.key !== targetKey); setItems(newPanes);
if (activeKey === targetKey && newPanes.length > 0) { setActiveKey(newPanes[0].key); } };
return ( <div style={{ padding: "24px", background: "#fff", borderRadius: "8px" }}> <Tabs type="editable-card" onChange={onChange} activeKey={activeKey} onEdit={onEdit} items={items} /> </div> ); }; export default EditableTabsEditor;
|
本 Demo 核心知识点:
type="editable-card"
: 开启“编辑模式”的开关。它会为标签页带来卡片式外观,并自动显示“新增”和“关闭”图标。- 受控模式: 这种模式 必须 是受控的。我们需要自己维护一个
items
数组的状态,以及一个 activeKey
的状态。 onEdit
回调: 这是编辑模式的灵魂。所有的新增和删除操作都会触发这个回调函数。我们需要在这里编写更新 items
状态数组的逻辑,从而实现对标签页的动态管理。
本节小结
通过对 Steps
和 Tabs
的学习,我们掌握了引导用户流程和组织页面内容的核心工具。Steps
适用于 引导用户完成一个线性的、有时序性的任务;而 Tabs
则适用于 将同一主题下的不同维度内容进行归类和分层展示,让用户可以自由切换。
理解它们的适用场景,是构建清晰、易用、不让用户迷路的应用界面的关键。