NavMenu.tsx.md 8.7 KB

NavMenu.tsx 文档

文件职责

NavMenu 是侧边导航菜单容器组件,负责:

  1. 组合导航区域:将业务功能区(BusinessZone)和系统功能区(SystemZone)组合在一起
  2. 布局管理:使用 Flexbox 垂直布局两个功能区
  3. 事件中继:接收父组件的 onMenuClick 回调并传递给子组件
  4. Ref 转发:为 SystemZone 提供 ref 引用(虽然当前未使用)

实现方式

1. 技术栈

  • React:组件化开发
  • Ant Design:Flex 布局组件
  • 组件组合:通过组合 BusinessZone 和 SystemZone 构建完整导航

2. 组件结构

NavMenu
├── Flex (垂直布局容器)
    ├── BusinessZone (业务功能区,可增长)
    │   ├── 患者管理
    │   ├── 检查
    │   ├── 图像处理
    │   └── 打印
    └── SystemZone (系统功能区,固定)
        ├── 配置/帮助
        └── 用户信息 (MeButton)

3. 布局策略

┌─────────────┐
│ BusinessZone│  ← flex-grow(占据剩余空间)
│             │
│  (滚动区域) │
│             │
├─────────────┤
│ SystemZone  │  ← 固定高度,底部对齐
└─────────────┘

关键点

  • 容器使用 h-full(height: 100%)填充父容器
  • BusinessZone 会自动增长并在内容过多时显示滚动条
  • SystemZone 固定在底部

实现思路

1. 组件组合模式(Component Composition)

NavMenu 不实现具体的导航逻辑,而是将两个专门的子组件组合在一起:

<Flex vertical className="h-full">
  <BusinessZone onMenuClick={onClick} />
  <SystemZone ref={systemZoneRef} onMenuClick={onClick} />
</Flex>

设计优势

  • 单一职责:NavMenu 只负责布局,不关心具体菜单内容
  • 可维护性:BusinessZone 和 SystemZone 可以独立开发和维护
  • 可测试性:每个组件可以单独测试
  • 灵活性:容易调整布局或替换子组件

2. 事件传递(Event Delegation)

NavMenu 作为事件中继,将父组件的回调传递给子组件:

const onClick = (key: string) => {
  onMenuClick?.(key);
};

// 传递给子组件
<BusinessZone onMenuClick={onClick} />
<SystemZone onMenuClick={onClick} />

作用

  • 统一事件处理接口
  • 保持数据流向清晰(单向数据流)
  • 便于在中间添加额外逻辑(如果需要)

3. Ref 引用(虽然未使用)

const systemZoneRef = useRef<HTMLDivElement>(null);
<SystemZone ref={systemZoneRef} onMenuClick={onClick} />

可能的用途

  • 测量 SystemZone 的高度
  • 动态调整 BusinessZone 的可用空间
  • 滚动控制或焦点管理

当前状态:已创建 ref 但未使用,可能是为未来功能预留

边界

输入

  • Props
    • onMenuClick?: (key: string) => void:菜单项点击回调函数

输出

  • 渲染输出:垂直排列的导航菜单,包括:

    • 业务功能区(BusinessZone)
    • 系统功能区(SystemZone)
  • 事件传递

    • onMenuClick 回调传递给子组件

职责边界

负责

  • ✅ 组合 BusinessZone 和 SystemZone
  • ✅ 使用 Flex 布局垂直排列子组件
  • ✅ 事件回调的传递
  • ✅ 为 SystemZone 提供 ref(虽然未使用)

不负责

  • ❌ 具体菜单项的渲染(由 BusinessZone 和 SystemZone 负责)
  • ❌ 菜单项的权限控制(由 BusinessZone 负责)
  • ❌ 用户状态管理(由 SystemZone 和 Redux 负责)
  • ❌ 业务流程切换逻辑(由父组件 BasicLayout 负责)

涉及概念

1. 组件组合(Component Composition)

将多个小组件组合成一个大组件,而不是在一个组件中实现所有功能。

优势

  • 关注点分离
  • 代码复用
  • 易于维护
  • 单一职责原则

示例

// 好的做法:组合
<NavMenu>
  <BusinessZone />
  <SystemZone />
</NavMenu>

// 不好的做法:单一组件实现所有功能
<NavMenu>
  {/* 所有菜单项都在这里硬编码 */}
</NavMenu>

2. 容器组件模式(Container Component)

NavMenu 是一个典型的容器组件:

  • 负责布局和组合
  • 不关心内部细节
  • 传递 props 和回调

对比

  • 容器组件:NavMenu(布局和组合)
  • 展示组件:BusinessZone、SystemZone(具体内容)

3. Props 传递(Props Drilling)

将 props 从父组件传递到子组件。

BasicLayout
    ↓ onMenuClick
NavMenu
    ↓ onMenuClick
BusinessZone / SystemZone

问题

  • 对于深层嵌套,可能需要多层传递
  • 可以通过 Context 或 Redux 解决

4. Flexbox 布局

使用 CSS Flexbox 实现灵活的布局。

<Flex vertical className="h-full">

关键属性

  • vertical:垂直方向排列(flex-direction: column)
  • h-full:高度 100%,填充父容器
  • 子元素可以使用 flex-grow 占据剩余空间

5. useRef Hook

创建一个可变的 ref 对象,其值在组件的整个生命周期内保持不变。

const systemZoneRef = useRef<HTMLDivElement>(null);

用途

  • 访问 DOM 元素
  • 存储不触发渲染的值
  • 父组件访问子组件的方法或属性

6. 可选链操作符(Optional Chaining)

onMenuClick?.(key);

等同于:

if (onMenuClick) {
  onMenuClick(key);
}

优势

  • 代码更简洁
  • 避免空值错误
  • TypeScript 友好

7. 事件委托(Event Delegation)

NavMenu 接收事件回调,然后传递给子组件,形成事件委托链。

数据流

用户点击菜单项
    ↓
BusinessZone/SystemZone 触发 onMenuClick
    ↓
NavMenu 的 onClick 函数
    ↓
父组件(BasicLayout)的 handleMenuClick
    ↓
Redux action dispatch
    ↓
状态更新

8. 单一职责原则(Single Responsibility Principle)

NavMenu 只负责一件事:组合和布局子组件。

好处

  • 代码简洁(仅约 20 行)
  • 易于理解
  • 易于测试
  • 变更影响小

注意事项

1. 未使用的 Ref

const systemZoneRef = useRef<HTMLDivElement>(null);

状态:已创建但从未使用

可能原因

  • 为未来功能预留
  • 开发过程中遗留的代码

建议

  • 如果确实不需要,可以移除以减少代码噪音
  • 如果有计划使用,添加 TODO 注释说明用途

2. 被注释的代码

文件中有大量被注释掉的代码:

// <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
// <div
//   style={{ width: '100%' }}
//   className="grid grid-rows-[1fr] h-0 flex-grow overflow-y-auto"
// >

问题

  • 影响代码可读性
  • 版本控制系统已经保存了历史

建议:移除被注释的代码,保持代码整洁

3. 事件处理函数命名

const onClick = (key: string) => {
  onMenuClick?.(key);
};

当前:函数名为 onClick,但实际是处理菜单点击

改进建议:更明确的命名,如 handleMenuClick

4. 组件简单性

NavMenu 非常简单,几乎只是一个布局包装器。

思考

  • 是否真的需要这个组件?
  • 可以考虑直接在 BasicLayout 中组合 BusinessZone 和 SystemZone

保留的理由

  • 提供了清晰的语义(NavMenu)
  • 便于未来扩展(如添加导航逻辑)
  • 符合组件化思想

使用场景

NavMenu 用于大屏幕(xl 和 lg 尺寸)的侧边栏导航:

// 在 BasicLayout 中
<Col xl={2} lg={2} md={0} sm={0} xs={0}>
  <NavMenu onMenuClick={handleMenuClick} />
</Col>

响应式策略

  • xl/lg:使用 NavMenu 侧边栏
  • md:使用 NavbarFloat 浮动按钮
  • sm/xs:使用 TabBar 底部导航

代码简化建议

由于 NavMenu 非常简单,可以考虑简化:

方案 1:移除中间函数

<BusinessZone onMenuClick={onMenuClick} />
<SystemZone onMenuClick={onMenuClick} />

直接传递 onMenuClick,不需要中间的 onClick 函数。

方案 2:移除未使用的 ref

const NavMenu: React.FC<NavMenuProps> = ({ onMenuClick }) => {
  return (
    <Flex vertical className="h-full">
      <BusinessZone onMenuClick={onMenuClick} />
      <SystemZone onMenuClick={onMenuClick} />
    </Flex>
  );
};

更加简洁,除非确实需要 ref。

相关文件

  • src/layouts/BasicLayout.tsx:使用 NavMenu 的父组件
  • src/layouts/BusinessZone.tsx:业务功能区子组件
  • src/layouts/SystemZone.tsx:系统功能区子组件
  • src/states/BusinessFlowSlice.ts:业务流程状态管理(最终接收菜单点击事件)