大家好。今天想聊聊最近修的一个看似简单,实则挺有意思的"坑"。
事情的起因是这样的:有用户反馈,访问 xxx 首页时,那三个最重要的套餐余量圈圈(V/D/S)经常玩"失踪"。运气好的话等个 5 秒,运气不好,盯着白屏看 30 秒,这三个圈圈才慢悠悠地弹出来。
30 秒?在这个短视频都嫌长的年代,30 秒足够用户把浏览器关掉三次了。这不仅仅是体验差,简直是劝退。
于是,我打开 Chrome DevTools,开启了这次的侦探之旅。
现场勘查与根因分析
打开 Network 面板,一刷新,果然看到了罪魁祸首:queryUsage 这个 API 的响应条长得像个马拉松跑道。
但我很快意识到,后端慢只是导火索,前端的"老实"才是帮凶。
翻看代码,我发现之前的逻辑非常"耿直":
// 之前的逻辑:老实人的悲剧
queryUsage().then((res) => {
showUsage.value = true // ⚠️ 划重点:拿到数据才肯把 UI 画出来
// 解析数据...
})这种写法的潜台词是:"在数据没回来之前,我绝对不会让用户看到任何东西。"
从技术角度说,这违反了**关键渲染路径(Critical Rendering Path)**的优化原则。我们将一个非关键资源(远程数据)强行塞到了关键渲染路径上,导致首屏渲染被阻塞。
从设计哲学的角度看,这是典型的同步思维错配。Web 是天生异步的,但我们却用写同步代码的逻辑去处理 UI——试图在一个不确定的网络环境中追求确定性的渲染顺序。结果就是:后端卡,前端陪着挂。
破局:从"等待"到"乐观"
修复的核心思路很简单:解耦。把"画界面"和"取数据"这两件事分开。
我们要采用骨架屏 + 渐进式加载的策略。哪怕数据还没到,盘子(UI 占位)得先摆好,给用户一种"由于我已经准备好了,只是菜还在路上"的心理暗示。
具体的"手术"步骤
这次重构不仅仅是改个状态位,还顺手做了一次逻辑瘦身。
1. 拿掉"双重保险",消除重复请求
我发现旧代码里有个很有意思的冗余:组件先调了一次快速缓存接口 queryUsageQuick(),然后又调了全量接口 queryUsage()。尴尬的是,queryUsage() 内部逻辑其实已经包含了读取缓存的步骤。 改动: 直接删掉外部的 queryUsageQuick 调用。信任 queryUsage() 的内部机制(缓存优先 + 后台刷新),少发一次请求,代码和网络都清爽了。
2. 拒绝阻塞,拥抱异步
之前的逻辑用了 await 死等结果。现在我们改用 Promise 链式调用或者非阻塞的写法。主线程该干嘛干嘛,等数据回来了再更新界面。
3. 立即渲染
showUsage 状态默认设为 true。组件一挂载,三个圈圈的骨架(或者上次的缓存数据)立马显示。
优化后的伪代码(Show me the code)
// 优化后:乐观 UI + 渐进增强
const showUsage = ref(true) // ✅ 哪怕没有数据,先让 UI 占位显示
function getAcctUsage() {
// 1. 调用 queryUsage,它内部封装了 "Cache First" 逻辑
// 2. 这里不再死等 await,而是让 Promise 在后台跑
queryUsage()
.then((res) => {
// 数据回来了?太好了,更新 UI
parseAndUpdateUsageData(res)
})
.catch((err) => {
// 出错了?处理错误,或者展示空状态
console.error("加载失败", err)
})
.finally(() => {
// 关掉加载中小圆点
stopLoadingIndicator()
})
// 函数立即返回,不阻塞主线程的其他渲染任务
}注:为了保持逻辑清晰,这里没有展示具体的 API 内部实现,重点在于调用方式的改变。
本质洞察:实际上没变快,但这更重要
这次优化最有趣的地方在于:客观上,原本那个慢得要死的 API 依然很慢。 并没有人去优化后端的数据库查询。
但是,用户的等待时间从 30 秒 变成了 <100 毫秒。
这就是**感知性能(Perceived Performance)的魔力。我们实际上是利用了乐观 UI(Optimistic UI)**的设计模式——先展示预期的状态(即使是骨架或旧数据),再异步去验证和更新。
这有点像你在餐厅点餐。好的服务员会先给你倒杯水(立即渲染),给你拿篮面包(缓存数据),让你有事可做,而不是让你干坐在那儿等到牛排煎好(阻塞渲染)。
总结一下前端优化的心法:
别做老实人:不要等所有数据都准备好了再渲染,那是后端思维。
视觉反馈即正义:用户不怕等,怕的是不知道在等什么(白屏)。
渐进式增强:先让页面能看(骨架/缓存),再让页面能用(交互),最后才是数据最新(实时更新)。
这次修复不仅解决了用户投诉,代码量还比之前少了十几行。这大概就是所谓的"降本增效"吧(笑)。