GridView 宫格加载渲染优化
系统首页使用 GridView 宫格模块,接口耗时本身并不高,但页面首次进入总耗时接近 22s,明显超出预期,用户反馈「打开首页要等很久」。
问题定位
优化前先分层定位耗时来源,避免一上来就盲目改代码:
| 阶段 | 耗时 | 说明 |
|---|---|---|
| 首次打开页面总耗时 | 约 20s-30s | 用户实际感知到的等待时间 |
| 本地资源加载 | 约 1s-2s | 排除本地静态资源问题 |
外链 Vue 资源下载 | 接近 15s | 网络链路影响明显,是最大单项耗时 |
| 去掉外链后总耗时 | 约 12s | 排除外链影响后,仍远高于预期 |
| 其中纯渲染耗时 | 8s-10s | 排除资源加载后剩余的“真实渲染成本” |
结论:问题不仅是资源加载,还存在页面渲染层面的结构性开销,两者叠加才导致了 22s 的总耗时,因此优化需要「资源加载」与「渲染逻辑」两个方向同时推进。
渲染层面的瓶颈
继续查看源码发现:
- 页面代码接近
2000行,逻辑高度集中在单个组件内; 100个 tab 存在大量重复节点与重复逻辑,每个 tab 几乎是相似结构的复制粘贴;- 可复用部分未封装,导致渲染成本随 tab 数量线性叠加,同时维护成本也偏高(改一处逻辑需要同步改 100 处)。
分析图如下
优化方案
| 手段 | 具体做法 | 解决的问题 |
|---|---|---|
| 提炼重复逻辑 | 将 100 个 tab 中重复的判断、计算逻辑抽取为公共函数 | 减少无效代码体积与重复执行开销 |
| 抽取公共节点结构 | 统一渲染模板,用配置数据驱动而非手写重复 DOM | 降低模板编译体积,减少重复的虚拟 DOM diff 成本 |
| 封装公共渲染函数 | 每个 tab 通过统一函数生成节点,避免独立拼装 | 减少组件实例数量与重复的生命周期开销 |
| 关键元素动态追加 | 部分非首屏必需元素通过 JS 动态追加,而非全部走模板编译 | 减少不必要的编译渲染开销,缩短首屏可交互时间 |
涉及公司源码,以下仅展示无敏感信息的示意图



优化结果与指标
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 首次进入总耗时 | 约 20s-30s | 降低约 7s-8s,整体等待时间显著缩短 |
| 纯渲染耗时 | 8s-10s | 明显下降(随重复节点与重复逻辑消除同步降低) |
| 页面代码规模 | 接近 2000 行、大量重复 | 通过公共函数与模板抽取后显著精简 |
| 维护成本 | 改一处需同步改 100 处 tab | 改公共逻辑/模板即可覆盖全部 tab |
通过渲染链路重构后,页面减少了编译阶段的冗余开销,渲染等待时间降低约 7s-8s,整体体验显著提升。
优化前后代码结构对比
| 维度 | 优化前 | 优化后 |
|---|---|---|
| 组件规模 | 单组件约 2000 行,逻辑集中 | 拆分公共函数 + 配置数据驱动,单文件复杂度显著下降 |
| Tab 渲染方式 | 100 个 tab 手写重复结构 | 统一渲染函数按配置遍历生成 |
| 改动成本 | 改一处逻辑需同步改 100 处 | 改公共函数/配置即可覆盖全部 tab |
| 非首屏元素 | 全部走模板编译 | 部分非首屏必需元素改为 JS 动态追加 |
优化思路示意
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 数量线性叠加编译开销,而是共享同一套渲染逻辑。
配置数据驱动示例(示意)
// 优化前:每个 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 时,只需修改配置数据即可正常渲染,无需改动公共渲染函数。
常见疑问
- 为什么先排查「资源加载」和「渲染」两个方向,而不是直接改代码?
22s的总耗时可能由多个独立因素叠加导致,如果不先分层定位,很容易把大量精力投入到收益有限的方向(例如只优化渲染,却忽略了外链下载占了近15s),分层定位能帮助优化资源投入到收益最大的环节。 - 抽取公共渲染函数后,是否会影响个别 tab 的差异化需求?
配置数据驱动并不意味着所有 tab 必须完全一致,可以在配置项中增加可选字段(如variant、extra),公共渲染函数内部按需处理差异分支,仍能覆盖少数特殊 tab 的个性化需求。 JS动态追加非首屏元素是否会影响可访问性或 SEO?
该页面为登录后的业务首页,不涉及 SEO 需求;可访问性方面需要确保动态追加的元素仍具备正确的语义标签与可聚焦特性,不能因为「动态追加」而遗漏无障碍属性。
后续建议
- 对外链资源(如
Vue等公共库)评估是否可迁移至更稳定的 CDN 节点或自建静态资源域名,进一步压缩约15s的外链下载耗时; - 类似「多 tab / 多宫格」重复结构的页面,建议在设计阶段就采用「配置数据 + 公共渲染函数」模式,而非等积累到 100 个 tab 才回头优化;
- 建立首屏耗时监控埋点,区分「资源加载」与「渲染耗时」两段上报,便于后续类似问题快速定位到具体阶段,而不必每次都从头分层排查。
相关链接
相关文章
老系统升级与兼容改造实践
公司原有业务系统采用 Node + jQuery 架构,长期运行后暴露出典型遗留系统问题:
防篡改水印
在各类管理后台、SaaS 平台或内部系统中,页面水印已经是非常常见的能力:一方面在截图时携带账号、姓名、时间等信息,降低截图外传的风险;另一方面在上传图片或文档预览时,通过水印标记上传者与时间,满足审计和合规需求。
内部插件 / 工具库开发实践
在企业前端项目中,utils、hooks、libs 这类通用能力通常分散在多个仓库,常见问题包括:
图片上传前的自定义水印实践
项目需要在图片上传前叠加动态水印。水印内容不是固定文案,而是由多个动态元素组成(如时间、地点、业务字段等)。
新商家系统性能优化实践
商家系统新版本上线后,团队持续针对构建效率与用户体验做了一轮系统性优化。本文记录核心方案与落地结果,供后续版本复用。
键盘弹起导致底部被顶起问题(H5 适配)
在移动端 H5 页面中,当用户聚焦 input、textarea 等可编辑元素、软键盘弹出时,常见表现包括:
Series
team documents
19 / 22