前端性能优化实战:那些年我踩过的坑
分享我在前端性能优化过程中踩过的坑和实战经验,从8秒加载时间优化到2秒的真实案例。
分享我在前端性能优化过程中踩过的坑和实战经验,从8秒加载时间优化到2秒的真实案例。
相关文章推荐功能即将上线
上周,我的老板拿着手机皱着眉头问我:"为什么我们的网站加载这么慢?"那一刻,我意识到是时候认真对待性能优化了。经过一个月的折腾,我把网站加载时间从8秒降到了2秒。今天想和大家分享一些实战经验,特别是那些我曾经踩过的坑。
刚开始我凭感觉优化了一堆东西,结果发现效果微乎其微。后来才知道,要先搞清楚瓶颈在哪里。
Google 的 Core Web Vitals 是个好东西,但别被这些指标吓到:
我用了 Lighthouse 和 Chrome DevTools 的 Performance 面板,发现主要问题是图片太大和JavaScript太多。
我之前直接上传了4MB的原图,结果加载慢得要死。后来学乖了:
// 我现在用的懒加载组件 const LazyImage = ({ src, alt }) => { const [isLoaded, setIsLoaded] = useState(false); const [isInView, setIsInView] = useState(false); const imgRef = useRef(); useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsInView(true); observer.disconnect(); } }, { threshold: 0.1 } ); if (imgRef.current) { observer.observe(imgRef.current); } return () => observer.disconnect(); }, []); return ( <div ref={imgRef} className="lazy-image-container"> {isInView && ( <img src={src} alt={alt} onLoad={() => setIsLoaded(true)} style={{ opacity: isLoaded ? 1 : 0 }} /> )} </div> ); };
这个组件帮我减少了60%的初始加载时间。还有,我现在都用 WebP 格式,体积比 JPEG 小30%左右。
之前我把整个管理后台的代码都打包进了主文件,结果普通用户也要下载几MB的管理代码。后来我学会了这样拆分:
// 路由级别的代码分割 const HomePage = lazy(() => import('./pages/HomePage')); const AboutPage = lazy(() => import('./pages/AboutPage')); // 组件级别的动态导入 const HeavyComponent = lazy(() => import('./components/HeavyComponent').then(module => ({ default: module.HeavyComponent })) ); // 条件加载管理面板 const AdminPanel = lazy(() => import('./components/AdminPanel').then(module => ({ default: module.AdminPanel })) ); function App() { const [isAdmin, setIsAdmin] = useState(false); return ( <div> <Suspense fallback={<div>Loading...</div>}> <HomePage /> {isAdmin && ( <Suspense fallback={<div>Loading admin panel...</div>}> <AdminPanel /> </Suspense> )} </Suspense> </div> ); }
这样拆分后,普通用户访问时只需要下载必要的代码,管理后台的代码只有在管理员登录时才会加载。
我之前有个问题:用户每次访问都要重新下载所有资源。后来我加上了 Service Worker:
// 我的缓存策略 const CACHE_NAME = 'my-app-cache-v1'; const urlsToCache = [ '/', '/static/js/main.js', '/static/css/main.css', '/static/images/logo.png' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(urlsToCache)) ); }); self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(response => { // 缓存命中,直接返回 if (response) { return response; } // 没命中,去网络请求 return fetch(event.request).then(response => { // 检查响应是否有效 if (!response || response.status !== 200 || response.type !== 'basic') { return response; } // 克隆响应,因为响应流只能用一次 const responseToCache = response.clone(); caches.open(CACHE_NAME) .then(cache => { cache.put(event.request, responseToCache); }); return response; }); }) ); });
这个简单的缓存策略让我的重复访问速度提升了80%。
我曾经有个列表页面,每次点击按钮整个列表都会重新渲染,性能差得要死。后来学会了这些技巧:
// 使用 React.memo 避免不必要的重渲染 const ExpensiveComponent = React.memo(({ data, onUpdate }) => { // 组件逻辑 }, (prevProps, nextProps) => { // 自定义比较函数,只有ID变化时才重新渲染 return prevProps.data.id === nextProps.data.id; }); // 使用 useMemo 缓存计算结果 const ExpensiveCalculation = ({ items }) => { const sortedItems = useMemo(() => { console.log('Sorting items...'); return items.sort((a, b) => a.value - b.value); }, [items]); // 只有items变化时才重新排序 return <div>{sortedItems.map(item => <div key={item.id}>{item.value}</div>)}</div>; }; // 使用 useCallback 缓存函数 const ParentComponent = ({ items }) => { const [count, setCount] = useState(0); const handleItemClick = useCallback((item) => { console.log('Item clicked:', item); }, []); // 空依赖数组表示函数永远不会改变 return ( <div> <button onClick={() => setCount(count + 1)}>Count: {count}</button> <ItemList items={items} onItemClick={handleItemClick} /> </div> ); };
这些优化让我的列表页面响应速度提升了3倍。
我之前还在用 HTTP/1.1,后来升级到 HTTP/2,发现多路复用真的很有用,可以同时加载多个资源。
还有资源预加载,我现在都会预加载关键资源:
<!-- 预加载关键资源 --> <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/critical.css" as="style"> <!-- 预取可能需要的资源 --> <link rel="prefetch" href="/next-page.js" as="script"> <link rel="prefetch" href="/next-page.css" as="style"> <!-- DNS 预解析 --> <link rel="dns-prefetch" href="//api.example.com"> <link rel="preconnect" href="//api.example.com" crossorigin>
这些简单的标签让我的页面加载速度又提升了20%。
我现在都用响应式图片,根据设备尺寸加载不同大小的图片:
// 响应式图片组件 const ResponsiveImage = ({ src, alt, sizes }) => { return ( <picture> <source media="(min-width: 1024px)" srcSet={`${src}?w=1200&f=webp 1x, ${src}?w=2400&f=webp 2x`} type="image/webp" /> <source media="(min-width: 768px)" srcSet={`${src}?w=800&f=webp 1x, ${src}?w=1600&f=webp 2x`} type="image/webp" /> <img src={`${src}?w=400&f=auto`} alt={alt} sizes={sizes} loading="lazy" decoding="async" /> </picture> ); };
这样移动设备不会下载桌面端的大图,节省了大量带宽。
我现在用这些工具监控性能:
我还设置了性能预算,防止代码膨胀:
{ "performanceBudget": { "totalSize": "250KB", "javascriptSize": "100KB", "cssSize": "50KB", "imageSize": "100KB", "fontSize": "50KB", "maxRequestCount": 20 } }
经过这一个月的折腾,我总结了几点心得:
最重要的是,性能优化不是一次性的工作,而是需要持续关注的过程。现在我每周都会检查性能数据,确保网站保持良好的状态。
希望我的经验能帮到大家,别再像我一样踩同样的坑了!