FORMA

GridView 宫格加载渲染优化

系统首页使用 GridView 宫格模块,接口耗时本身并不高,但页面首次进入总耗时接近 22s,明显超出预期,用户反馈「打开首页要等很久」。

问题定位

优化前先分层定位耗时来源,避免一上来就盲目改代码:

阶段耗时说明
首次打开页面总耗时20s-30s用户实际感知到的等待时间
本地资源加载1s-2s排除本地静态资源问题
外链 Vue 资源下载接近 15s网络链路影响明显,是最大单项耗时
去掉外链后总耗时12s排除外链影响后,仍远高于预期
其中纯渲染耗时8s-10s排除资源加载后剩余的“真实渲染成本”

结论:问题不仅是资源加载,还存在页面渲染层面的结构性开销,两者叠加才导致了 22s 的总耗时,因此优化需要「资源加载」与「渲染逻辑」两个方向同时推进。

渲染层面的瓶颈

继续查看源码发现:

  • 页面代码接近 2000 行,逻辑高度集中在单个组件内;
  • 100 个 tab 存在大量重复节点与重复逻辑,每个 tab 几乎是相似结构的复制粘贴;
  • 可复用部分未封装,导致渲染成本随 tab 数量线性叠加,同时维护成本也偏高(改一处逻辑需要同步改 100 处)。

分析图如下

GridView分析图

优化方案

手段具体做法解决的问题
提炼重复逻辑将 100 个 tab 中重复的判断、计算逻辑抽取为公共函数减少无效代码体积与重复执行开销
抽取公共节点结构统一渲染模板,用配置数据驱动而非手写重复 DOM降低模板编译体积,减少重复的虚拟 DOM diff 成本
封装公共渲染函数每个 tab 通过统一函数生成节点,避免独立拼装减少组件实例数量与重复的生命周期开销
关键元素动态追加部分非首屏必需元素通过 JS 动态追加,而非全部走模板编译减少不必要的编译渲染开销,缩短首屏可交互时间

涉及公司源码,以下仅展示无敏感信息的示意图

GridView解决图1GridView解决图2GridView解决图3

优化结果与指标

指标优化前优化后
首次进入总耗时20s-30s降低约 7s-8s,整体等待时间显著缩短
纯渲染耗时8s-10s明显下降(随重复节点与重复逻辑消除同步降低)
页面代码规模接近 2000 行、大量重复通过公共函数与模板抽取后显著精简
维护成本改一处需同步改 100 处 tab改公共逻辑/模板即可覆盖全部 tab

通过渲染链路重构后,页面减少了编译阶段的冗余开销,渲染等待时间降低约 7s-8s,整体体验显著提升。

优化前后代码结构对比

维度优化前优化后
组件规模单组件约 2000 行,逻辑集中拆分公共函数 + 配置数据驱动,单文件复杂度显著下降
Tab 渲染方式100 个 tab 手写重复结构统一渲染函数按配置遍历生成
改动成本改一处逻辑需同步改 100 处改公共函数/配置即可覆盖全部 tab
非首屏元素全部走模板编译部分非首屏必需元素改为 JS 动态追加

优化思路示意

mermaid
flowchart LR
  A[页面渲染耗时高] --> B[100个tab重复渲染]
  B --> C[提炼公共判断/计算逻辑]
  B --> D[抽取统一渲染模板]
  C --> E[配置数据驱动渲染]
  D --> E
  E --> F[公共渲染函数生成节点]
  F --> G[非首屏元素改为JS动态追加]
  G --> H[渲染耗时下降7-8s]

核心思路是把「100 个几乎相同的 tab」从「重复手写」转变为「配置 + 公共函数」,渲染成本不再随 tab 数量线性叠加编译开销,而是共享同一套渲染逻辑。

配置数据驱动示例(示意)

javascript
// 优化前:每个 tab 手写重复结构(示意,实际有 100 处类似代码)
function renderTab1() {
  return `<div class="tab-item">...tab1 专属结构...</div>`;
}
function renderTab2() {
  return `<div class="tab-item">...tab2 专属结构...</div>`;
}
// ... 重复 100 次

// 优化后:配置驱动 + 公共渲染函数
const tabConfigs = [
  { id: 1, title: "分类一", icon: "icon1" },
  { id: 2, title: "分类二", icon: "icon2" },
  // ... 100 项配置数据,而非 100 段重复代码
];

function renderTabItem(config) {
  return `<div class="tab-item" data-id="${config.id}">
    <img src="${config.icon}" />
    <span>${config.title}</span>
  </div>`;
}

const html = tabConfigs.map(renderTabItem).join("");

配置化之后,新增/调整 tab 只需修改 tabConfigs 数据,不再需要新增或修改渲染函数本身。

回归检查清单

  • 全部 100 个 tab 在优化后功能表现与优化前一致(点击、切换、数据展示均正常);
  • 首次进入页面的总耗时、纯渲染耗时符合优化后的预期指标;
  • 非首屏动态追加的元素在滚动到可视区域后能正确展示,不出现闪烁或错位;
  • 弱网环境下验证外链资源加载失败时的兜底表现(如降级为本地资源或提示);
  • 后续新增 tab 时,只需修改配置数据即可正常渲染,无需改动公共渲染函数。

常见疑问

  1. 为什么先排查「资源加载」和「渲染」两个方向,而不是直接改代码?
    22s 的总耗时可能由多个独立因素叠加导致,如果不先分层定位,很容易把大量精力投入到收益有限的方向(例如只优化渲染,却忽略了外链下载占了近 15s),分层定位能帮助优化资源投入到收益最大的环节。
  2. 抽取公共渲染函数后,是否会影响个别 tab 的差异化需求?
    配置数据驱动并不意味着所有 tab 必须完全一致,可以在配置项中增加可选字段(如 variantextra),公共渲染函数内部按需处理差异分支,仍能覆盖少数特殊 tab 的个性化需求。
  3. JS 动态追加非首屏元素是否会影响可访问性或 SEO?
    该页面为登录后的业务首页,不涉及 SEO 需求;可访问性方面需要确保动态追加的元素仍具备正确的语义标签与可聚焦特性,不能因为「动态追加」而遗漏无障碍属性。

后续建议

  • 对外链资源(如 Vue 等公共库)评估是否可迁移至更稳定的 CDN 节点或自建静态资源域名,进一步压缩约 15s 的外链下载耗时;
  • 类似「多 tab / 多宫格」重复结构的页面,建议在设计阶段就采用「配置数据 + 公共渲染函数」模式,而非等积累到 100 个 tab 才回头优化;
  • 建立首屏耗时监控埋点,区分「资源加载」与「渲染耗时」两段上报,便于后续类似问题快速定位到具体阶段,而不必每次都从头分层排查。

相关链接