三种性能瓶颈

类型 表现 解决方向
加载慢 首屏空白久 bundle 拆分、SSR、图片优化
交互卡 点击/输入有迟滞 减少 re-render、防抖、懒加载
滚动顿 长列表滑不动 虚拟化、骨架占位

每种用法都不一样,先确认你是哪一种

测量工具

  1. React DevTools Profiler:录一次操作 → 看每个组件 render 耗时
  2. Chrome Performance:录一次操作 → 看主线程 long task
  3. Lighthouse:跑一次 → 看 LCP / FID / CLS 三大指标
  4. Web Vitals 库:生产环境采集真实用户数据

减少 re-render

找到无效 re-render

DevTools Profiler 录一次 → 看哪些组件 render 时间 > 0 但 props 没变(高亮显示"Why did this render")。

解法 1:state 下沉

function App() {
  const [text, setText] = useState('');
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <HeavyChart />    {/* text 变 → 整个 App re-render → HeavyChart 也 */}
    </>
  );
}

修:把 input 拆出去

function App() {
  return <><MyInput /><HeavyChart /></>;
}

function MyInput() {
  const [text, setText] = useState('');
  return <input value={text} onChange={e => setText(e.target.value)} />;
}

text 现在住在 MyInput——它变 HeavyChart 完全不受影响。这是最常被忽略的优化

解法 2:memo + useCallback

只在子组件确实昂贵且 props 引用稳定才有效。详见 第 08 篇

解法 3:React Compiler(推荐)

// next.config.js(Next.js 15+ 起从 experimental 提到顶层;以官方文档为准)
{ reactCompiler: true }

编译器自动加 memo。新项目优先开启,配合 eslint-plugin-react-compiler 修违反 React Rules 的代码。详见 第 08 篇

Bundle 大小

# Next.js 自带
npm run build   # 看输出,每条路由的 First Load JS

@next/bundle-analyzer 可视化。

减小手段

  • 动态 importconst Heavy = dynamic(() => import('./Heavy'))——只在用到时加载
  • 避免 barrel fileimport { x } from '@/lib' 拉一堆——直接 import { x } from '@/lib/x'
  • 替换大库moment (200kb) → date-fns / dayjs (~10kb);lodash 整包 → lodash-es 按需
  • 图片优化:Next.js <Image> 自动生成 WebP / AVIF + 多尺寸
  • 字体优化next/font 自动 self-host + 子集化

长列表虚拟化

10k 行表格、聊天历史——只渲染可视区域 + buffer:

npm i @tanstack/react-virtual
const rowVirtualizer = useVirtualizer({
  count: 10000,
  getScrollElement: () => parentRef.current,
  estimateSize: () => 40,
});

return (
  <div ref={parentRef} style={{ height: 500, overflow: 'auto' }}>
    <div style={{ height: rowVirtualizer.getTotalSize(), position: 'relative' }}>
      {rowVirtualizer.getVirtualItems().map(v => (
        <div key={v.key} style={{
          position: 'absolute', top: 0, transform: `translateY(${v.start}px)`,
        }}>
          {data[v.index]}
        </div>
      ))}
    </div>
  </div>
);

输入防抖 / 节流

const debouncedQ = useDebounce(query, 300);
useEffect(() => { search(debouncedQ); }, [debouncedQ]);

用户连续输入只触发一次搜索。

useTransition 替代法(React 18+):

const [isPending, startTransition] = useTransition();
const [filter, setFilter] = useState('');

const onChange = (e) => {
  setFilter(e.target.value);   // 紧急 update
  startTransition(() => {
    setFilteredList(heavyFilter(e.target.value));   // 非紧急
  });
};

紧急更新(输入框跟手)和非紧急更新(重新过滤大列表)分离——输入永远顺滑。

Web Vitals

监控真实用户体验:

// app/web-vitals.ts
'use client';
import { useReportWebVitals } from 'next/web-vitals';

export function WebVitals() {
  useReportWebVitals((metric) => {
    fetch('/api/vitals', { method: 'POST', body: JSON.stringify(metric) });
  });
}

关注:

  • LCP < 2.5s(最大内容绘制)
  • INP < 200ms(交互到下次绘制)
  • CLS < 0.1(视觉稳定性)

服务端优化

慢主要不在 React 而在数据库 / 后端:

  • DB 加索引
  • N+1 → join / dataloader
  • 缓存(Redis / Next.js Data Cache)
  • CDN

90% 的"网站卡"问题其实在后端。先看后端响应时间,再优化前端。

性能优化的优先级

1. 先测、确认瓶颈
2. 算法 / 数据结构 / 缓存
3. 减少 React re-render(state 下沉、memo)
4. Bundle 拆分、懒加载
5. 渲染策略(SSR / SSG / ISR)
6. 微观优化(避免内联对象、useCallback)

倒着来 = 浪费时间。

→ 下一篇 SSR / CSR / SSG / ISR