页面响应速度直接关系到用户的去留。渲染变慢通常不是单一原因造成的,而是一连串环节共同作用的结果。想要切实改善体验,需要把资源加载、列表展示、状态管理和构建输出这几个方面放在一起通盘考虑,同时留意那些容易被忽略的细节。
从用户发出请求到页面出现关键内容,这一段等待期决定了第一印象。优化的核心是尽量缩短浏览器在真正绘制前必须完成的工作。
样式表和同步脚本是阻碍首次绘制的主要因素。对于首屏用不到的那些样式,可以单独拆成文件,利用媒体属性或异步加载让它们稍后就绪;暂时不需要立即执行的脚本,应该加上 async 或者 defer 特性,让 HTML 的解析不被中断。
preload 能帮助浏览器提前获取首屏需要的关键字体或图片。不过,预加载需要谨慎使用,如果一口气给几十个文件都标记了高优先级,反而会导致网络资源的争夺,最终让最重要的请求得不到及时响应。
检查优化效果的时候,打开开发者工具中的 Performance 面板,完整录制一次加载过程,重点对比首次内容绘制和最大内容绘制的时间变化。很多人在优化时只盯着压缩脚本,却忘了关注字体的加载时机,这会导致文字在加载完成后突然更换样式,甚至带来布局上的跳动。
列表中的数据一旦上千,即使每个条目再简单,浏览器也会因为节点数量过多而变得反应迟钝。虚拟滚动的基本逻辑是只渲染当前屏幕内能看到的条目,用空白占位来维持滚动条的长度,从而大幅降低 DOM 数量。
针对虚拟滚动,行业中已经有不少经过验证的库,比如 React 生态中的 react-window,以及 Vue 场景下的 vue-virtual-scroller,它们都处理了动态高度、定位等难缠的边界问题。除非业务场景实在特殊,否则不建议自行编写虚拟滚动逻辑,其中的细节远比表面看起来复杂。
如果列表里的每一项高度都一致,使用默认配置就能顺畅运行。可一旦高度各不相同,就必须启用动态测量,并预先设置一个合理的估算高度,不然滚动速度稍快,页面内容就会出现明显的跳动错位。同时也要清楚虚拟滚动的局限性,对于依赖键盘操作或者屏幕阅读器的表格控件,虚拟化反而会带来可访问性问题,这时候更好的选择可能是服务端分页,或者用节流思路实现的无限滚动。
页面卡顿很多时候源于组件被反复地、没有必要地重新渲染。尤其是全局数据被放在很顶层的地方时,一个细微改动都可能拉着整棵组件树一起刷新。
在 React 中,给普通的展示组件加上 React.memo 能够拦住无关的更新;使用 useMemo 可以缓存那些计算量大的数据,而 useCallback 能保持函数引用不变。在 Vue 项目里,则要善于使用计算属性,并且对频繁触发的事件做节流或防抖处理。
一个比较常见的错误做法,是把所有数据都一股脑地放进全局状态里。更合适的方式是细化状态的层级:像弹窗开关、表单输入这类只属于局部的内容,应当保留在组件内部;真正需要跨模块共享的数据,才考虑放入全局 Store。此外,使用 React DevTools 的 Profiler 或者 Vue 的性能分析工具,能很容易地定位到到底是谁在反复触发渲染,从而有针对性地修改。
完成了运行时的调优,也不能忽略静态资源的体积和数据请求的结构。这两者同样直接影响加载速度。
首先,定期审视打包后的产物,利用代码分割将那些不常用的页面或者第三方库单独打包,确保首屏只加载必要的 JavaScript。其次,把图片转换成 WebP 这类体积更小的格式,并通过合适的尺寸裁剪,避免加载一张远超显示区域的大图。最后,控制接口返回的数据量,按需请求字段,或者在后端做数据聚合,避免前端去拼接多次请求的结果。
判断数据交付是否合理,可以打开 Network 面板观察一下页面初始加载时发起的请求数量和大小。一个常见误区是过分追求 HTTP 请求数尽可能少,结果把不同业务的数据强行合并成一个接口,导致首屏等待时间反而变长。数据的拆分与合并,应当依据实际使用场景来做决定。
当数据量在几百条以内,且需要支持快速的全文搜索或者排序时,分页会更稳妥,因为它保留了完整的 DOM,对键盘导航友好。而当数据量轻松破千,用户更习惯连续滚动浏览时,虚拟滚动能带来更顺畅的体验。如果数据量特别大,比如上万条,建议优先考虑服务端分页加上前端虚拟滚动的组合。
懒加载需要额外的网络往返来获取后续的代码块,如果分割过细,就会产生大量的小请求,阻塞关键资源的加载。检查一下是不是把体积本就不大的组件也拆开了,或者懒加载的触发时机太靠前。合理的做法是只拆分体积较大且不是首屏必需的模块,同时配合 prefetch 在空闲时提前获取即将用到的部分。
如果动画只涉及位移、缩放或透明度这些属性,CSS 动画会交给合成器处理,能够跳过布局和绘制阶段,表现通常更好。JS 动画则更灵活,适合控制复杂的交互逻辑,但要注意避免在每一帧里修改会引起布局的属性,比如宽高或 top 值。无论用哪种方式,都尽量使用 transform 和 opacity 来实现动效。
渲染性能的提升不是靠某个单一技巧就能解决的。建议从首屏资源入手,控制阻塞渲染的内容,再逐步处理列表渲染和状态管理的问题,最后审视构建产物体积。每一项优化完成后,都用性能面板记录前后的数据变化,做有针对性的对比。从最容易着手的地方开始,比如为脚本添加 async 标记,或者替换一张过大图片,往往能带来立竿见影的改善。