问题不是突然出现的。
它更像是某个凌晨三点的警告灯,
不是刺耳地响,而是安静地亮了一下。
最先注意到的是测试同事。
“阅读器在长章节下,滚动有点卡。”
这句话被丢进群里的时候,没有加感叹号,也没有截图。
太普通了。
林晨却在看到的那一刻,心里轻轻沉了一下。
他打开测试环境,加载了一本内部用来压测的书。
章节很长,
没有花哨格式,
只是纯文本。
滚轮向下。
前两屏,很顺。
第三屏开始,有一点迟滞。
不是掉帧,是那种你说不清楚,但手能感觉到的慢。
林晨停下滚动,打开 DevTools。
不是 Network,
是 Performance。
他点下 Record。
滚动。
停止。
一条时间线慢慢铺开。
紫色的 scripting,
绿色的 rendering,
黄色的 painting。
没有哪一段“炸掉”,
但每一段,都比他预期的多了一点。
“嗯……”
他把 timeline 放大。
罪魁祸首并不复杂。
watch(
() => readingSettings.value,
() => {
recalcReadingStyle()
},
{ deep: true }
)
deep watch。
他盯着这段代码看了几秒,没骂自己。
这是一个当时看来很合理的选择。
设置项集中在一个对象里,
深度监听,
统一响应。
逻辑很干净。
代价,是每一次滚动时的连锁反应。
滚动本身不改设置。
但滚动会触发进度更新。
进度更新,会触发依赖。
依赖里,
藏着一次 style 重新计算。
浏览器不会告诉你:
“你不该这样写。”
它只会慢慢地,
开始喘。
林晨没有立刻修。
他先做了一件事——
写下结论。
不是 bug,是结构性损耗。****
下午的评审会上,
他没有直接放 Performance 面板。
他先问了一个问题。
“你们在用阅读器的时候,
有没有觉得哪一刻开始不顺?”
苏雨想了想。
“翻页还好,
就是读久了,会有点累。”
这不是心理问题。
这是帧率。
林晨这才把时间线投出来。
“现在的架构下,”
他说,“我们把阅读设置和阅读行为耦合在了一起。”
“用户滚动,本该只是浏览器的事,
但现在,每一次滚动,
都会经过我们的响应系统。”
他在白板上写:
滚动
↓
进度更新
↓
watch 触发
↓
样式重算
↓
DOM diff
“链条不长,”
他说,“但它一直在跑。”
张明皱眉:“那为什么之前没问题?”
林晨没有马上回答。
他等了一秒。
“因为之前,
我们还没把系统逼到边界。”
“测试数据少,
阅读时间短,
用户行为简单。”
“现在不是代码变差了,
是使用方式变真实了。”
苏雨没有插话。
她只是看着那条链。
“能优化吗?”
她问。
“能。”
林晨点头。
“但不是补丁。”
他切到另一页。
const readingStyle = computed(() => {
// 只依赖设置
})
“第一步,
把所有与滚动无关的东西,
从响应链里拆出去。”
“第二步,
进度更新只记录数值,
不触发 UI 级别的响应。”
“第三步,”
他停了一下,“限制响应系统的‘影响范围’。”
这不是一行代码。
这是一次思想上的收紧。
会后,苏雨留下来。
“所以,”她说,“我们现在的感觉,其实是系统在提醒我们。”
“对。”
林晨点头。
“它没崩,
但它在告诉你:
我不能再随便被用了。”
那天晚上,林晨改得很慢。
他没有一口气重构完。
每拆一个依赖,
他就重新跑一次 Performance。
不是为了指标,
而是为了确认——
系统有没有更安静一点。****
凌晨一点。
滚动。
时间线变短了。
紫色少了,
绿色也收敛了。
不是完美,
但顺了。
他在 commit message 里写了一句话:
decouple reading behavior from style reactivity
没有人会在意这句话。
但他知道,这是一次转向。
第二天早上,苏雨在群里发了一条测试反馈:
滚动顺多了。
那种“读久了会累”的感觉没了。
林晨看着这句话,没有笑。
他只是关掉群聊,继续写代码。
他心里很清楚——
真正复杂的系统,不会靠一次优化就安全。****
但至少现在,
他们听懂了性能在说什么。