技术
15 min read

前端性能优化实战:从 Lighthouse 到 Core Web Vitals

2025-01-16by LZG
#Performance#Frontend#Optimization

引言

前端性能优化是提升用户体验的关键。本文将从 Core Web Vitals 指标出发,结合实际项目经验,分享系统性的性能优化策略。

1. Core Web Vitals 核心指标

1.1 LCP (Largest Contentful Paint)

定义:页面主要内容加载完成的时间

目标:< 2.5s

优化策略

<!-- 1. 优化关键资源加载 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preconnect" href="https://cdn.example.com">

<!-- 2. 图片优化 -->
<img 
  src="hero.webp" 
  loading="eager"
  width="1200" 
  height="600"
  fetchpriority="high"
  alt="Hero image"
>

<!-- 3. 避免阻塞渲染 -->
<script defer src="/analytics.js"></script>

1.2 FID (First Input Delay)

定义:用户首次交互到页面响应的时间

目标:< 100ms

优化策略

// 1. 代码分割
import { lazy } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

// 2. 避免长任务
function processLargeArray(array) {
  const result = [];
  const chunkSize = 1000;
  
  for (let i = 0; i < array.length; i += chunkSize) {
    const chunk = array.slice(i, i + chunkSize);
    result.push(...processChunk(chunk));
    
    // 让出主线程
    if (i % 5000 === 0) {
      await new Promise(resolve => setTimeout(resolve, 0));
    }
  }
  
  return result;
}

// 3. 使用 Web Worker
const worker = new Worker('data-processor.js');
worker.postMessage(largeData);

1.3 CLS (Cumulative Layout Shift)

定义:页面内容在加载过程中的视觉稳定性

目标:< 0.1

优化策略

/* 1. 为图片预留空间 */
.image-container {
  aspect-ratio: 16 / 9;
  background-color: #f0f0f0;
}

/* 2. 使用 font-display */
@font-face {
  font-family: 'Custom Font';
  src: url('/fonts/custom.woff2');
  font-display: swap;
}

/* 3. 避免动态插入内容 */
.ad-banner {
  min-height: 250px;
}

2. 资源优化

2.1 图片优化

// 使用 Next.js Image 组件
import Image from 'next/image';

function OptimizedImage() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero image"
      width={1200}
      height={600}
      priority
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
    />
  );
}

// 响应式图片
<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>

2.2 代码分割

// 路由级别分割
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

// 组件级别分割
const Chart = lazy(() => import('./components/Chart'));
const Table = lazy(() => import('./components/Table'));

// 预加载关键路由
const prefetchDashboard = () => import('./pages/Dashboard');

2.3 Tree Shaking

// ✅ 使用命名导入
import { debounce } from 'lodash-es';

// ❌ 避免默认导入
import _ from 'lodash';

// 配置 package.json
{
  "sideEffects": false,
  "exports": {
    ".": {
      "import": "./index.js",
      "require": "./index.cjs"
    }
  }
}

3. 缓存策略

3.1 HTTP 缓存

// Next.js 缓存配置
export async function getData() {
  const res = await fetch('https://api.example.com/data', {
    next: {
      revalidate: 3600, // 1 小时
      tags: ['products']
    }
  });
  
  return res.json();
}

// 按需重新验证
import { revalidateTag } from 'next/cache';

export async function POST() {
  revalidateTag('products');
  return Response.json({ revalidated: true });
}

3.2 Service Worker 缓存

// 缓存策略
const CACHE_STRATEGIES = {
  cacheFirst: async (request) => {
    const cached = await caches.match(request);
    return cached || fetch(request);
  },
  
  networkFirst: async (request) => {
    try {
      const network = await fetch(request);
      const cache = await caches.open('v1');
      cache.put(request, network.clone());
      return network;
    } catch {
      return caches.match(request);
    }
  },
  
  staleWhileRevalidate: async (request) => {
    const cache = await caches.open('v1');
    const cached = await cache.match(request);
    
    const fetchPromise = fetch(request).then(response => {
      cache.put(request, response.clone());
      return response;
    });
    
    return cached || fetchPromise;
  }
};

4. 渲染优化

4.1 虚拟滚动

import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }) {
  const parentRef = useRef<HTMLDivElement>(null);
  
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
    overscan: 5
  });
  
  return (
    <div ref={parentRef} style={{ height: '500px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px` }}>
        {virtualizer.getVirtualItems().map(virtualItem => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`
            }}
          >
            {items[virtualItem.index]}
          </div>
        ))}
      </div>
    </div>
  );
}

4.2 React 优化

// 1. 使用 memo
const ExpensiveComponent = memo(({ data }) => {
  return <div>{/* 渲染逻辑 */}</div>;
});

// 2. 使用 useMemo
function Component({ items }) {
  const sorted = useMemo(() => {
    return [...items].sort((a, b) => a.value - b.value);
  }, [items]);
  
  return <List items={sorted} />;
}

// 3. 使用 useCallback
function Parent() {
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []);
  
  return <Child onClick={handleClick} />;
}

// 4. 避免不必要的渲染
function List({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          <Item data={item} />
        </li>
      ))}
    </ul>
  );
}

5. 监控与分析

5.1 性能监控

// Web Vitals 监控
import { onCLS, onFID, onLCP, onTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  const url = 'https://analytics.example.com/vitals';
  
  if (navigator.sendBeacon) {
    navigator.sendBeacon(url, body);
  } else {
    fetch(url, { body, method: 'POST', keepalive: true });
  }
}

onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
onTTFB(sendToAnalytics);

5.2 自定义性能指标

// 测量特定操作
function measurePerformance(name, fn) {
  const start = performance.now();
  
  const result = fn();
  
  const end = performance.now();
  const duration = end - start;
  
  console.log(`${name} took ${duration.toFixed(2)}ms`);
  
  return result;
}

// 使用 Performance API
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(`${entry.name}: ${entry.duration}ms`);
  }
});

observer.observe({ entryTypes: ['measure', 'navigation'] });

6. 实战案例

6.1 电商首页优化

问题:首屏加载时间 4.5s,LCP 评分 45

优化措施

  1. 图片懒加载和 WebP 格式
  2. 关键 CSS 内联
  3. 第三方脚本延迟加载
  4. 服务端渲染关键内容

结果:首屏加载时间降至 1.8s,LCP 评分提升至 92

6.2 管理后台优化

问题:列表页面渲染缓慢,FID 超标

优化措施

  1. 虚拟滚动实现
  2. 分页加载
  3. 防抖搜索
  4. Web Worker 处理大数据

结果:列表渲染时间从 2s 降至 200ms,FID 评分提升至 95

结语

前端性能优化是一个持续的过程,需要结合具体场景选择合适的策略。通过监控 Core Web Vitals 指标,系统性地优化资源加载、渲染性能和用户体验,我们可以构建出高性能的 Web 应用。记住:性能优化不是一次性的工作,而是需要持续关注和改进的过程。