Skip to content
第 164 / 250 章前端⏱ 10 分钟阅读

第 164 章:性能优化与最佳实践

学习目标

  • 掌握 React 性能优化手段
  • 学会 Profiler 工具使用
  • 理解 Server Components 性能优势
  • 学会代码分割与懒加载

一、React 性能模型

React 通过 Virtual DOM diff 决定哪些 DOM 需要更新。优化就是减少 diff 工作量避免不必要的渲染

二、React.memo 浅比较

tsx
import { memo } from 'react';

interface Props {
  user: User;
  onClick: (id: number) => void;
}

const UserCard = memo(function UserCard({ user, onClick }: Props) {
  return (
    <div onClick={() => onClick(user.id)}>
      {user.name}
    </div>
  );
});

触发重渲染的情况:

  • Props 引用变化(对象/函数)
  • Context value 变化
  • 父组件重渲染

三、useMemo / useCallback

tsx
function Parent() {
  const [count, setCount] = useState(0);
  const [user, setUser] = useState<User>({ id: 1, name: 'Tom' });

  // ❌ 每次新建对象
  const config = { theme: 'dark', user };

  // ✅ 引用稳定
  const config = useMemo(() => ({ theme: 'dark', user }), [user]);

  // ❌ 每次新建函数
  const handleClick = (id: number) => console.log(id);

  // ✅ 引用稳定
  const handleClick = useCallback((id: number) => console.log(id), []);

  return <Child config={config} onClick={handleClick} />;
}

四、代码分割

4.1 路由级分割(Next.js 自动)

每个 app/路由/page.tsx 默认就是独立 chunk。

4.2 组件级分割

tsx
import { lazy, Suspense } from 'react';

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

function Dashboard() {
  return (
    <Suspense fallback={<div>加载图表...</div>}>
      <HeavyChart />
    </Suspense>
  );
}

4.3 动态 import

tsx
'use client';
import dynamic from 'next/dynamic';

const Chart = dynamic(() => import('./Chart'), {
  loading: () => <p>加载中</p>,
  ssr: false,  // 禁用 SSR
});

五、图片优化

tsx
import Image from 'next/image';

// 远程图片(需要在 next.config 配置域名)
<Image
  src="https://example.com/x.jpg"
  alt="描述"
  width={800}
  height={600}
  priority   // 首屏图片
/>

// 本地图片
import hero from './hero.jpg';
<Image src={hero} alt="hero" placeholder="blur" />

自动优化:

  • WebP / AVIF 格式
  • 响应式尺寸
  • 懒加载(默认)

六、字体优化

tsx
// app/layout.tsx
import { Inter } from 'next/font/google';

const inter = Inter({ subsets: ['latin'], display: 'swap' });

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh-CN" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}

七、避免常见反模式

7.1 列表虚拟化

tsx
// 长列表(>1000 项)用虚拟化
import { useVirtualizer } from '@tanstack/react-virtual';

function BigList({ items }: { items: Item[] }) {
  const parentRef = useRef<HTMLDivElement>(null);
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: virtualizer.getTotalSize() }}>
        {virtualizer.getVirtualItems().map(v => (
          <div key={v.key} style={{ transform: `translateY(${v.start}px)` }}>
            {items[v.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

7.2 避免内联函数(大列表)

tsx
// ❌ 每次渲染新建函数
{items.map(item => (
  <Item key={item.id} onClick={() => handle(item)} />
))}

// ✅ 用 useCallback 或 data-属性
const handle = useCallback((e) => {
  const id = e.currentTarget.dataset.id;
  // ...
}, []);

{items.map(item => (
  <Item key={item.id} data-id={item.id} onClick={handle} />
))}

7.3 避免 Context 拆分不当

tsx
// ❌ 一个大 Context,任何字段变化都触发
<AppContext.Provider value={{ user, theme, count, ... }}>

// ✅ 拆分
<UserContext.Provider value={user}>
  <ThemeContext.Provider value={theme}>
    <CountContext.Provider value={count}>
      {children}
    </CountContext.Provider>
  </ThemeContext.Provider>
</UserContext.Provider>

八、Server Components 性能

  • Server Components 在服务端运行,不发送到浏览器
  • 大幅减少 JS bundle 体积
  • 适合数据展示、不需要交互的部分

九、Web Vitals

指标含义目标
LCP最大内容绘制< 2.5s
FID首次输入延迟< 100ms
CLS累积布局偏移< 0.1
INP交互到下一次绘制< 200ms

9.1 测量

tsx
'use client';
import { useReportWebVitals } from 'next/web-vitals';

export function WebVitals() {
  useReportWebVitals((metric) => {
    console.log(metric);
    // 上报到分析服务
  });
}

十、Profiler 调试

tsx
import { Profiler } from 'react';

<Profiler id="Sidebar" onRender={(id, phase, actualDuration) => {
  if (actualDuration > 16) {
    console.warn(`${id} 渲染慢:${actualDuration}ms`);
  }
}}>
  <Sidebar />
</Profiler>

十一、构建优化

js
// next.config.mjs
export default {
  experimental: {
    optimizePackageImports: ['antd', 'lodash'],  // 按需引入
  },
  compiler: {
    removeConsole: process.env.NODE_ENV === 'production',  // 移除 console
  },
};

十二、本章小结

优化手段场景
React.memo纯展示组件
useMemo重计算
useCallback稳定函数引用
懒加载大组件/路由
虚拟列表长列表
Server Components数据展示
Image 组件图片优化

动手练习

  1. 用 React.memo 优化一个列表
  2. 用 lazy 分割大组件
  3. 用 Profiler 找出慢组件
  4. 用 next/image 优化图片
  5. 测一下 Web Vitals

推荐阅读


下一章:第 165 章:实战总结与资源

本站基于 VitePress 构建 · 由 Codebook 团队维护