Skip to content

前端性能规范

本文档从历史前端性能报告中提炼长期开发规范,适用于 w7panel-ui 的页面、API 封装、组件、hooks、store 和资源加载。

整体要求

前端性能优化优先处理用户能感知的慢路径:

场景风险要求
页面首屏多个接口串行导致等待放大互不依赖的请求并行,依赖关系显式写清
列表页DOM 节点过多、重复渲染分页、筛选、虚拟滚动或后端 limit
轮询和定时器页面关闭后仍请求统一 hooks 管理并在生命周期中清理
全局状态重复拉取 namespace、用户信息等使用 Pinia 和带 TTL 的缓存
文件、日志、终端大文本、连接和编辑器实例重用专用组件,关闭时释放资源

页面性能预算

页面开发时先区分首屏关键数据和延迟数据。

数据类型加载时机要求
首屏必需数据页面进入立即加载互不依赖时并行,有 loading 和 error
次要卡片数据首屏后加载或进入视口加载不阻塞页面主体
弹窗/抽屉数据打开时加载关闭时清理,必要时缓存
大列表详情用户展开或点击时加载不在列表初始化时逐项串行请求
日志/终端/文件内容用户进入能力时加载可取消、可关闭、可释放

常用预算:

指标默认要求
首屏接口不依赖的请求并行
普通列表直接 DOM 渲染小于 1000 条
轮询间隔默认不低于 5 秒,实时场景需说明
弹窗重组件默认按需创建
大文本渲染使用编辑器、虚拟列表或流式组件

API 请求

约定:

  • 页面初始化时,互不依赖的请求使用 Promise.all 或统一请求 hook 并行处理。
  • 不在列表循环中串行请求每一项详情;必须逐项请求时限制并发或分批请求。
  • 优先使用 src/utils/api.tspanelApik8sproxysrc/api/ 方法。
  • URL 参数使用 axios paramsencodeURIComponent,避免因为特殊字符产生重复失败请求。
  • 全局 axios 超时由 src/api/interceptor.ts 管理,单接口需要更长时间时在调用处明确说明。
  • 同一 GET 请求在短时间内可能被多个组件触发时,应复用已有 store、hook 缓存或防重机制。

请求组织

场景推荐写法原因
多个互不依赖接口Promise.all缩短首屏等待
同一资源多组件使用Pinia store 或 hook 缓存避免重复请求
列表项详情展开时加载、分批加载或后端聚合避免 N+1
搜索输入debounce + 取消上一次请求避免请求风暴
切换 namespace取消旧请求,刷新依赖状态避免旧数据覆盖新数据

推荐模式:

ts
const [apps, namespaces, metrics] = await Promise.all([
  fetchApps(),
  namespaceStore.fetchNamespaceList(),
  fetchMetrics(),
]);

需要避免:

ts
for (const item of list) {
  item.detail = await fetchDetail(item.name);
}

分批并发模式:

ts
const batchSize = 10;
for (let i = 0; i < list.length; i += batchSize) {
  const batch = list.slice(i, i + batchSize);
  await Promise.all(batch.map((item) => fetchDetail(item.name)));
}

搜索请求模式:

ts
const controller = new AbortController();
await panelApi.get('/apps/search', {
  params: { keyword },
  signal: controller.signal,
});
controller.abort();

Hooks、轮询和生命周期

  • 定时器、轮询、WebSocket、xterm、CodeMirror、ECharts 实例必须在 onUnmounted 中清理。
  • keep-alive 页面需要在 onDeactivated 中停止轮询,在 onActivated 中按需恢复。
  • 弹窗、抽屉内的重组件关闭时必须释放连接和实例;必要时使用 unmountOnClose
  • 轮询间隔按业务实时性设置,避免多个组件同时轮询同一接口。
  • 使用 src/hooks/timer.tssrc/hooks/request.ts 等已有 hooks 时,优先复用其取消、缓存和清理能力。

检查项:

能力必须清理
setIntervalsetTimeouttimer id
轮询请求polling handle、AbortController
WebSocket、终端socket、xterm 实例
编辑器CodeMirror、Monaco 或 Codeblitz 实例
图表ECharts instance
Wujie 事件bus listener

生命周期规则

组件类型初始化清理
普通页面onMounted 或路由参数准备后onUnmounted
keep-alive 页面onActivated 恢复可见数据onDeactivated 停止轮询
弹窗/抽屉visible 变为 true 后加载visible 变为 false 后取消请求和释放重实例
Wujie 微应用容器子应用挂载后注册事件容器销毁时注销事件
文件/日志/终端用户进入能力后连接关闭 tab、弹窗或路由离开时断开

watch 使用要求:

  • watch 里发请求时必须处理连续变更,避免旧响应覆盖新状态。
  • 监听 namespace、pod、container、path 等关键参数时,参数为空应直接短路。
  • 使用 immediate: true 时确认不会和 onMounted 重复请求。

状态和缓存

  • namespace、用户信息、权限、K8s 基础信息等跨页面状态放 Pinia。
  • localStorage/sessionStorage key 必须使用模块前缀,遵守 auth-state.md
  • 缓存必须定义 TTL、强制刷新入口和清理入口。
  • 不把同一后端响应长期复制到多个 store 或组件变量中,避免状态不同步。
  • token、密码、密钥、OIDC code 不缓存到非必要位置,不输出到 console。

示例:

ts
await namespaceStore.fetchNamespaceList();
await namespaceStore.fetchNamespaceList(true); // 强制刷新

缓存设计

字段要求
key包含模块、namespace、资源名、用户上下文
ttl按数据变化频率设置,不能永久有效
force refresh页面刷新、用户手动刷新或 namespace 切换可绕过缓存
clear登出、token 失效、权限变化时能清理
stale behavior过期期间是显示旧数据并刷新,还是清空后加载,需要明确

缓存适用性:

数据适合缓存注意
namespace 列表适合namespace 变更后强制刷新
用户信息和权限适合token 刷新、登出时清理
监控实时数据短 TTL不要长期缓存
日志流不适合长期缓存关闭连接优先
文件内容谨慎保存、外部变化和编码问题要处理

列表和渲染

  • 1000 条以上列表必须使用分页、虚拟滚动或后端 limit,不直接渲染完整 DOM。
  • 表格 row-keyv-for :key 使用稳定业务字段,不使用数组 index。
  • 大型 YAML、日志、文本文件使用专用编辑器或终端组件,不放到普通 textarea 长时间渲染。
  • ECharts 只注册需要的图表类型和组件,新增类型同步全局组件注册文档。
  • 图片、应用图标、预览图等可延迟资源使用懒加载或按需加载。
  • 计算量大的过滤、排序和格式化结果应使用 computed 或后端过滤,不在模板里重复计算。

示例:

vue
<tr v-for="item in list" :key="item.metadata?.uid || item.name">
  ...
</tr>

<a-image :src="item.icon" loading="lazy" />

列表策略

规模策略
小于 200 条普通表格即可
200 到 1000 条分页、筛选、减少列渲染和复杂 formatter
1000 条以上虚拟滚动或后端分页
不可预估规模默认后端分页或 limit

表格优化:

  • row-key 使用 metadata.uid、资源名、路径等稳定字段。
  • 避免在模板中调用复杂函数,提前用 computed 或数据预处理。
  • 操作列中的弹窗、菜单、tooltip 不要为每行提前创建重组件。
  • 大量标签、图标、进度条会放大渲染成本,需要按需展示或折叠。

图表优化:

  • ECharts 初始化前确认容器可见且有尺寸。
  • 页面切换、弹窗关闭时调用 dispose
  • 高频数据更新使用节流,不要每个点都触发完整 setOption
  • 图表数据超过当前视图需要时做采样或聚合。

组件和资源

  • 公共组件要避免在 props 变化时重复发起完整初始化。
  • 抽屉和弹窗中的大组件按需创建,关闭后释放。
  • 路由级页面和低频能力优先异步加载。
  • 大依赖新增前必须确认是否已有替代组件或按需导入能力。
  • 图标优先使用项目已有图标库,避免为单个图标引入整包。

组件初始化

组件内部不要在 createdsetup 或模块顶层直接发起和显示状态无关的大请求。推荐按场景触发:

组件触发时机
页面主体路由参数和 store 初始化完成后
Drawer/Modalvisible=true
Tab 面板tab 首次激活时
折叠详情用户展开时
微应用能力微应用 ready 后

资源加载

  • 图片使用懒加载、错误兜底和固定尺寸,避免布局抖动。
  • 大型 JSON、YAML、日志文本按需加载,避免打进首屏 bundle。
  • 新增第三方库前检查是否支持 tree-shaking 和按需导入。
  • 静态常量和大枚举不要在多个组件重复定义。

反模式清单

反模式风险替代方案
页面 onMounted 串行 await 多个接口首屏等待累加Promise.all 或拆分首屏/延迟数据
v-for :key="index"更新错乱、重复渲染稳定业务 key
每行创建弹窗或编辑器DOM 和实例过多单例弹窗 + 当前行状态
关闭弹窗不取消请求旧响应污染状态AbortController 或 hook cancel
watch 里无条件请求参数变化导致请求风暴参数校验 + debounce + 去重
长日志放普通 textarea页面卡顿终端、虚拟列表或专用编辑器
多组件各自读取 localStorage状态分散auth 工具或 Pinia store

验证方式

提交前端性能相关改动时至少执行:

bash
npm run build
rg "setInterval|setTimeout|addEventListener|\\$on|new WebSocket|echarts\\.init" w7panel-ui/src
rg ":key=\"index\"|:key='index'|console\\.log" w7panel-ui/src

涉及列表、文件、日志、终端或图表时补充验证:

  • 首屏请求是否并行,Network 中是否出现不必要串行瀑布。
  • 关闭弹窗、切换路由、退出页面后是否停止轮询和 WebSocket。
  • 1000 条以上数据是否仍能滚动和筛选。
  • 大文本、大 YAML、日志流是否不会阻塞页面。
  • 刷新 token、namespace 切换、缓存强制刷新后页面数据是否一致。

评审清单

提交前端性能相关 PR 前自查:

检查项通过标准
请求首屏请求无不必要串行;列表无 N+1 串行请求
取消路由切换、弹窗关闭、namespace 切换时请求可取消或旧响应不会覆盖新状态
生命周期timer、WebSocket、事件、图表、编辑器实例均清理
列表大列表有分页、虚拟滚动或后端 limit
keyv-for 和表格 row-key 使用稳定业务字段
缓存key、TTL、强刷、清理逻辑明确
资源新依赖、新图表、新图片不会进入不必要首屏路径
文档新增公共性能模式同步更新本规范或前端约定