1
0

refactor: 前端性能优化 — 倒计时组件隔离、React memoization 链路

- 新建 RefreshCountdown 组件,内部持有 timer,消除 App 每秒重渲染
- TargetBoard 分组逻辑 useMemo 化,避免 targets 引用不变时重复计算
- TargetGroup 加 React.memo,阻断无效渲染
- TrendChart 加 React.memo + chartData useMemo,避免 recharts 不必要重绘
- OverviewTab 统计项去掉 Card 包裹,改用纯 CSS 实现视觉效果
- 同步更新 refresh-control 和 target-detail-drawer spec

性能提升:消除每秒全组件树重渲染,减少 DOM 节点数
This commit is contained in:
2026-05-15 12:02:39 +08:00
parent d6a77b2c6e
commit 86b8cf1950
9 changed files with 152 additions and 63 deletions

View File

@@ -20,7 +20,15 @@ HeadMenu operations 区域 SHALL 提供 RadioGroup 组件供用户选择刷新
- **THEN** 系统 SHALL 立即触发一次数据刷新,然后应用新的刷新间隔
### Requirement: 倒计时显示
RadioGroup 右侧 SHALL 显示距下次自动刷新的倒计时文本。
RadioGroup 右侧 SHALL 显示距下次自动刷新的倒计时文本。倒计时逻辑 SHALL 封装在独立的 `RefreshCountdown` 组件中App 组件 SHALL NOT 持有每秒更新的 `now` state。
#### Scenario: RefreshCountdown 组件封装
- **WHEN** Dashboard 页面渲染
- **THEN** 倒计时显示 SHALL 由独立的 `RefreshCountdown` 组件负责,该组件内部持有 `now` state 和每秒 `setInterval`,渲染边界限制在该组件内部
#### Scenario: RefreshCountdown props
- **WHEN** RefreshCountdown 组件渲染
- **THEN** 组件 SHALL 接收 `dashboardUpdatedAt: number``refreshInterval: number``isFetching: boolean``isManualRefresh: boolean``onRefresh: () => void` 作为 props
#### Scenario: 短时间格式
- **WHEN** 距下次刷新剩余时间小于 60 秒
@@ -38,6 +46,17 @@ RadioGroup 右侧 SHALL 显示距下次自动刷新的倒计时文本。
- **WHEN** 数据正在刷新isFetching=true 且 isLoading=false
- **THEN** 倒计时文本 SHALL 显示为"刷新中..."
### Requirement: App 组件渲染隔离
App 组件 SHALL NOT 持有任何高频更新的 state如每秒更新的时钟确保 App 的重渲染频率与数据刷新频率一致(默认 30 秒一次)。
#### Scenario: App 无 now state
- **WHEN** App 组件渲染
- **THEN** App SHALL NOT 包含 `useState` 管理的时钟 state也 SHALL NOT 包含每秒触发的 `setInterval`
#### Scenario: App 重渲染频率
- **WHEN** Dashboard 处于自动刷新模式
- **THEN** App 组件的重渲染 SHALL 仅由 TanStack Query 的 refetch 触发(频率等于用户选择的刷新间隔),而非每秒触发
### Requirement: 手动刷新按钮
选择"手动"模式时,倒计时区域 SHALL 替换为刷新按钮。

View File

@@ -72,6 +72,36 @@ TrendChart 组件 SHALL 仅接收数据 props不处理 loading 状态。
- **WHEN** TrendChart 接收空数组
- **THEN** 组件 SHALL 显示"暂无趋势数据"占位文本
#### Scenario: TrendChart memo 包裹
- **WHEN** TrendChart 的父组件重渲染但 data prop 引用未变
- **THEN** TrendChart SHALL 跳过重渲染(通过 React.memo shallow compare
#### Scenario: chartData useMemo
- **WHEN** TrendChart 渲染
- **THEN** 内部 `chartData` 转换结果 SHALL 通过 `useMemo` 缓存,依赖为 `[data]`data 引用不变时不重新计算
### Requirement: TargetBoard 分组 memoize
TargetBoard 组件的分组计算 SHALL 使用 useMemo 缓存,避免 targets 引用不变时重复计算分组。
#### Scenario: 分组结果 useMemo
- **WHEN** TargetBoard 渲染
- **THEN** 分组逻辑Map 构建 + sortSHALL 通过 `useMemo` 缓存,依赖为 `[targets]`
#### Scenario: targets 引用不变时跳过分组
- **WHEN** TargetBoard 因父组件重渲染而重渲染,但 targets prop 引用未变
- **THEN** 分组计算 SHALL 返回缓存结果,不重新执行 Map 构建和排序
### Requirement: TargetGroup 渲染优化
TargetGroup 组件 SHALL 使用 React.memo 包裹,在 props 引用不变时跳过重渲染。
#### Scenario: TargetGroup memo 包裹
- **WHEN** TargetBoard 重渲染但某个分组的 targets 数组引用未变
- **THEN** 对应的 TargetGroup SHALL 跳过重渲染(通过 React.memo shallow compare
#### Scenario: TargetGroup props 稳定性
- **WHEN** TargetGroup 渲染
- **THEN** 其 propscolumns、name、targets、onTargetClickSHALL 全部具有引用稳定性columns 通过 useMemo、name 为 string 原始值、targets 通过分组 useMemo、onTargetClick 通过 useCallback
### Requirement: StatusBar 参数化
StatusBar 组件 SHALL 支持可配置的格数。
@@ -189,7 +219,7 @@ Drawer 内部 SHALL 使用 TDesign Tabs 组织概览和记录两个面板。Tabs
#### Scenario: 统计区左右布局卡片
- **WHEN** 概览面板渲染且有统计数据
- **THEN** 面板 SHALL 在"统计"区域使用 4 列 × 2 行的 Row/Col 布局,每个统计项使用 Card 包裹Card 内标题左对齐、数值右对齐,数值使用普通文本字号
- **THEN** 面板 SHALL 在"统计"区域使用 4 列 × 2 行的 Row/Col 布局,每个统计项使用 `<div className="overview-stat-card">` 包裹,通过 CSS 类实现背景色和内边距视觉效果
#### Scenario: 统计区内容
- **WHEN** 概览面板渲染