前端性能优化,是每个前端工程师的必修课。
我最近接手了一个项目,性能很差:首屏加载要5秒,页面切换卡顿,列表滚动掉帧。用户抱怨不断,老板要求优化。我花了两周时间,把性能从"不能用"优化到"很流畅"。
本文通过这个真实项目的性能优化案例,对比React和Vue两个框架的优化方法,从加载性能、运行时性能、渲染性能、网络性能等多个维度,分享从慢到快的完整优化过程。
一、项目背景和问题
先说说项目背景。
1. 项目情况
这是一个中后台管理系统,功能包括:
- 数据看板:各种图表和统计
- 列表管理:数据表格,支持筛选、排序、分页
- 表单管理:复杂的表单,支持动态字段
- 用户管理:权限、角色、用户管理
技术栈:
- 前端:React 17 + Ant Design 4
- 后端:Node.js + MySQL
- 构建:Webpack 4
项目已经运行了两年,代码量越来越大,性能越来越差。
2. 性能问题
主要的性能问题:
- 首屏加载慢:5秒以上,白屏时间长
- 页面切换卡顿:切换页面要1-2秒
- 列表滚动掉帧:大数据量列表滚动不流畅
- 表单响应慢:输入框输入有延迟
- 打包体积大:主包超过2MB
3. 优化目标
我定的优化目标:
- 首屏加载 < 2秒
- 页面切换 < 300ms
- 列表滚动 60fps
- 输入响应 < 16ms
- 主包体积 < 500KB
二、性能分析
优化之前,先做性能分析,找到瓶颈。
1. 性能分析工具
我用了这些工具:
- Lighthouse:整体性能评分和建议
- Chrome DevTools:Performance面板分析运行时性能,Network面板分析加载
- Webpack Bundle Analyzer:分析打包体积
- React DevTools Profiler:分析React组件渲染性能
2. 分析结果
分析后发现的主要问题:
加载性能:
- 打包体积大:主包2.3MB,gzip后800KB
- 没有代码分割:所有页面都打在一个包里
- 第三方库太大:moment.js、lodash全量引入
- 没有缓存:静态资源没有合理的缓存策略
运行时性能:
- 不必要的重渲染:父组件状态变化,所有子组件都重新渲染
- 列表没有虚拟化:1000条数据一次性渲染
- 表单没有优化:每次输入都触发整个表单重渲染
- 计算属性没有缓存:每次渲染都重新计算
网络性能:
- 接口请求多:一个页面发十几个请求
- 没有请求合并:相关的接口分开请求
- 没有缓存:相同的数据重复请求
- 图片太大:没有压缩和懒加载
三、加载性能优化
先优化加载性能。
1. 代码分割
这是最有效的优化手段。
优化前: 所有页面都打在一个包里,主包2.3MB。
优化后: 用React.lazy和Suspense做路由级别的代码分割,每个页面单独打包。
// 优化前
import Dashboard from './pages/Dashboard';
import UserList from './pages/UserList';
// 优化后
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const UserList = React.lazy(() => import('./pages/UserList'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/users" element={<UserList />} />
</Routes>
</Suspense>
);
}效果: 主包从2.3MB降到400KB,首屏只加载需要的页面代码。
Vue中的对应方案: Vue用异步组件实现代码分割:
const Dashboard = () => import('./pages/Dashboard.vue');2. 第三方库优化
优化前: moment.js全量引入(含所有语言包),lodash全量引入。
优化后:
- moment.js换成day.js(体积小很多,API兼容)
- lodash按需引入,只引入用到的函数
- Ant Design按需引入,只引入用到的组件
// 优化前
import moment from 'moment';
import _ from 'lodash';
import { Button, Input, Table } from 'antd';
// 优化后
import dayjs from 'dayjs';
import debounce from 'lodash/debounce';
import Button from 'antd/es/button';效果: 第三方库体积减少了60%。
3. 资源压缩
- 开启gzip压缩(服务端配置)
- 图片压缩,用WebP格式
- 字体文件子集化,只包含用到的字符
- 移除未使用的CSS(PurgeCSS)
4. 缓存策略
- 静态资源设置长期缓存(Cache-Control: max-age=31536000)
- 文件名加hash,内容变化时文件名变化
- HTML不缓存,确保能获取最新版本
- 接口数据缓存,避免重复请求
5. 预加载和预获取
- 关键资源用preload提前加载
- 下一个页面可能用到的资源用prefetch预获取
- 首屏图片优先加载,非首屏图片懒加载
四、运行时性能优化
加载快了,运行时也要快。
1. 避免不必要的重渲染
这是React性能优化的重点。
优化前: 父组件状态变化,所有子组件都重新渲染,即使props没变。
优化后:
- 用React.memo包裹纯组件,props不变就不重新渲染
- 用useMemo缓存计算结果
- 用useCallback缓存函数引用,避免子组件因为函数引用变化而重渲染
// 优化前
function Parent() {
const [count, setCount] = useState(0);
const handleClick = () => setCount(c => c + 1);
const data = computeData(); // 每次渲染都重新计算
return <Child data={data} onClick={handleClick} />;
}
// 优化后
const Child = React.memo(function Child({ data, onClick }) {
return <div onClick={onClick}>{data}</div>;
});
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => setCount(c => c + 1), []);
const data = useMemo(() => computeData(), []);
return <Child data={data} onClick={handleClick} />;
}Vue中的对应方案:
- 用v-once渲染不需要更新的内容
- 用v-memo缓存组件
- 计算属性(computed)自动缓存
- 函数式组件(Vue 3中默认优化)
2. 列表虚拟化
优化前: 1000条数据一次性渲染,DOM节点多,滚动卡顿。
优化后: 用react-window做列表虚拟化,只渲染可见区域的项。
import { FixedSizeList as List } from 'react-window';
function VirtualList({ data }) {
const Row = ({ index, style }) => (
<div style={style}>{data[index].name}</div>
);
return (
<List
height={500}
itemCount={data.length}
itemSize={50}
width="100%"
>
{Row}
</List>
);
}效果: 1000条列表,DOM节点从1000个降到20个,滚动流畅。
Vue中的对应方案: 用vue-virtual-scroller。
3. 表单优化
优化前: 表单有50个字段,每次输入都触发整个表单重渲染,输入有延迟。
优化后:
- 表单字段拆分成独立组件,每个字段自己管理状态
- 用uncontrolled组件,减少重渲染
- 输入防抖,避免频繁触发校验
- 复杂表单用Formily或React Hook Form等优化过的表单库
// 优化前:整个表单一个组件,每次输入都重渲染所有字段
function BigForm() {
const [form, setForm] = useState({});
const handleChange = (key, value) => setForm({ ...form, [key]: value });
return (
<div>
<Input value={form.name} onChange={e => handleChange('name', e.target.value)} />
<Input value={form.email} onChange={e => handleChange('email', e.target.value)} />
{/* 50个字段 */}
</div>
);
}
// 优化后:每个字段独立组件,自己管理状态
function Field({ label, value, onChange }) {
const [localValue, setLocalValue] = useState(value);
const handleChange = e => {
setLocalValue(e.target.value);
onChange(e.target.value);
};
return (
<div>
<label>{label}</label>
<Input value={localValue} onChange={handleChange} />
</div>
);
}4. 大数据量处理
- 大数据量的计算用Web Worker,不阻塞主线程
- 复杂计算分批执行,用requestIdleCallback
- 数据分页,不要一次性加载所有数据
- 用虚拟滚动,只渲染可见数据
五、渲染性能优化
1. 避免布局抖动
- 批量读取和写入DOM,不要交替读写
- 用transform和opacity做动画,不触发重排
- 避免在滚动事件中修改样式
- 用will-change提示浏览器优化
2. 图片优化
- 图片尺寸和显示尺寸一致
- 用WebP格式,体积更小
- 懒加载,非首屏图片不加载
- 用占位图,避免布局抖动
3. CSS优化
- 避免使用昂贵的CSS属性(box-shadow、filter等)
- 减少选择器的复杂度
- 避免使用@import
- 用CSS containment隔离渲染区域
六、网络性能优化
1. 接口优化
- 合并请求:相关的接口合并成一个
- 接口聚合:后端提供BFF层,聚合多个接口
- 减少不必要的字段:接口只返回需要的字段
- 分页加载:大数据量分页,不要一次性返回
2. 请求缓存
- 相同的请求做缓存,避免重复请求
- 用SWR或React Query管理请求缓存
- 接口数据预加载,进入页面时数据已经准备好了
3. HTTP优化
- 用HTTP/2,多路复用
- 启用gzip/brotli压缩
- 用CDN加速静态资源
- DNS预解析,减少DNS查询时间
七、React vs Vue 优化对比
说说React和Vue在性能优化上的差异。
1. 渲染优化
React:
- 需要手动优化:React.memo、useMemo、useCallback
- 灵活性高,但需要开发者有优化意识
- 不当的写法容易导致性能问题
- React 18的并发模式,提供了更多优化手段
Vue:
- 自动优化:编译时优化,响应式系统精确更新
- 不需要手动memo,默认就比较快
- 对开发者更友好,不容易写出性能差的代码
- Vue 3的Proxy响应式,性能更好
总结: Vue默认性能更好,React需要手动优化但上限更高。
2. 代码分割
两者都支持,方式类似:
- React:React.lazy + Suspense
- Vue:异步组件 + Suspense(Vue 3)
3. 列表虚拟化
都需要第三方库:
- React:react-window、react-virtualized
- Vue:vue-virtual-scroller
4. 状态管理
React:
- Redux、Zustand、Jotai等
- 需要注意选择器,避免不必要的重渲染
- 状态管理的性能取决于使用方式
Vue:
- Pinia、Vuex
- 响应式系统自动优化,不需要手动处理
- 默认性能就很好
5. 构建优化
两者都用Vite或Webpack,优化方式类似:
- 代码分割
- 按需引入
- 压缩和缓存
- Tree Shaking
八、优化效果
说说优化后的效果。
1. 加载性能
- 首屏加载:从5秒降到1.5秒,提升70%
- 主包体积:从2.3MB降到400KB,减少83%
- 白屏时间:从3秒降到0.5秒
2. 运行时性能
- 页面切换:从1.5秒降到200ms,提升87%
- 列表滚动:从30fps升到60fps
- 输入响应:从100ms降到10ms以内
3. 用户体验
- 用户抱怨明显减少
- 页面操作流畅,没有卡顿感
- 数据看板加载快,交互响应及时
4. Lighthouse评分
- 性能:从30分升到90分
- 可访问性:从70分升到95分
- 最佳实践:从80分升到95分
九、性能优化的一般思路
总结一下性能优化的一般思路。
1. 先测量,再优化
不要盲目优化,先用工具测量,找到瓶颈。
- Lighthouse看整体评分
- DevTools看具体问题
- Bundle Analyzer看体积
- Profiler看渲染性能
2. 优先优化影响最大的
- 20%的问题导致80%的性能问题
- 先优化影响最大的瓶颈
- 不要在细枝末节上浪费时间
3. 加载性能和运行时性能都要管
- 加载性能:用户多久能看到页面
- 运行时性能:用户操作是否流畅
- 两者都重要,不能只优化一个
4. 建立性能监控
- 上线后持续监控性能
- 建立性能预算,防止性能退化
- 每次发布都跑性能测试
- 用真实用户数据(RUM)监控
5. 不要过度优化
- 优化是有成本的,会增加代码复杂度
- 只优化真正的瓶颈
- 优化后要测试,确保没有引入bug
- 可读性和可维护性也很重要
十、写在最后
这次性能优化,让我对前端性能有了更深的理解。
性能优化不是一蹴而就的,需要持续的关注和投入。从加载到运行时,从渲染到网络,每个环节都有优化的空间。
React和Vue,各有各的优化方式。React需要手动优化,但灵活性高;Vue自动优化,对开发者更友好。但无论用什么框架,性能优化的核心思路是一样的:找到瓶颈,针对性优化,持续监控。
2022年了,前端性能越来越受到重视。用户对性能的期望越来越高,慢一点就会流失用户。作为前端工程师,我们要把性能优化当成日常工作的一部分,而不是出了问题才去救火。
最后,用一句话总结:"性能优化没有银弹,测量、定位、优化、验证,一步一步来。加载要快,运行要流畅,网络要高效,用户体验才会好。"
愿你的项目,又快又稳。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录