跳至正文

前端页面优化的实战记录

事情是这样的,上个月我接了个后台管理系统的重构活儿,React + TypeScript + Ant Design Pro,听起来挺常规对吧?结果一上线就被运营大姐吐槽“点个按钮卡三秒”,我当场血压就上来了。拿Chrome的Lighthouse一跑,Performance分数42,首屏加载时间6.8秒,好家伙,这数据发到技术群里能被笑一年。

先说说最离谱的问题。这个项目用了大量的ECharts图表,而且每个图表都是在组件挂载时直接new ECharts实例。我打开Network面板一看,echarts.min.js 1.2MB全量加载,再加上各个图表配置项里引用的地图JSON数据,光图表相关就占了2.8MB。更蠢的是,首页其实只有两个折线图,但代码里import了所有图表类型。解决方案其实很粗暴:按需加载。我把import * as echarts from ‘echarts’改成了import { init, LineChart, TooltipComponent } from ‘echarts/core’。这一步直接从1.2MB砍到320KB。地图数据也做了懒加载,只在用户打开“地理分布”Tab时才fetch对应的GeoJSON。改完之后首屏JS体积从4.6MB降到2.1MB,效果立竿见影。

然后是图片资源的坑。项目里有个轮播图组件,用的是png格式,每张图2-3MB,一共8张。我寻思着这玩意又不是摄影作品,直接全转成WebP,用Sharp跑了个批处理脚本。转完之后每张图300-400KB,质量肉眼看不出区别。但有个细节要注意:Safari不支持WebP,所以我在nginx配置里加了判断逻辑,根据Accept请求头里的image/webp来决定返回什么格式。具体配置长这样:

“`
location ~* \.(jpg|jpeg|png)$ {
add_header Vary Accept;
if ($http_accept ~* “image/webp”) {
rewrite (.*)\.(jpg|jpeg|png)$ $1.webp break;
}
}
“`

这里有个坑,rewrite后面一定要加break,不然会循环重写导致500。

接下来是React层面的优化。我翻了下组件树,发现一个列表页面每行都有一个下拉菜单,每个菜单里又有十几个选项。这些下拉组件都是直接写在循环里的,每次数据刷新都会全部重新渲染。用React DevTools的Profiler跑了一下,单个列表项渲染耗时12ms,200条数据就是2.4秒的阻塞。解决办法是把下拉菜单抽成独立的Memo组件,并且用useMemo缓存选项列表。具体代码大概是:

“`tsx
const MemoizedDropdown = React.memo(({ options, value, onChange }) => {
const memoizedOptions = useMemo(() =>
options.map(opt => ({ label: opt.name, value: opt.id })),
[options]
);
return