人人都会AI编程

22.3 Gzip / Brotli 压缩与 CDN 加速

更新时间:2026-07-10

React 应用卡顿通常不是单一原因造成的,而是一个或多个环节同时出现问题。开发者需要建立系统的排查思路,快速定位瓶颈类型并采取对应的优化策略。按照性能消耗的来源,可以将瓶颈分为三类:渲染瓶颈、计算瓶颈、网络瓶颈

22.3.1 渲染瓶颈:组件重绘次数过多或耗时过长

典型表现

  • 页面滚动、输入框输入、动画有明显延迟。
  • 简单交互(如点击展开/收起)导致整个页面卡住半秒以上。
  • React DevTools 的 Profiler 面板显示某些组件的渲染耗时远超 16ms(60fps 的单帧预算)。

排查方法

  1. 使用 React DevTools Profiler

开启录制,触发可疑交互,然后分析火焰图。重点关注:

  • 哪些组件在当前交互中发生了不必要重渲染?
  • 同一组件的每次渲染耗时是否离谱?
  • 渲染的提交原因是什么?(Hooks 变更、Props 变更、Context 变更)
  1. 浏览器 Performance 面板

录制一段交互,查看 Main 线程的火焰图。搜索 commitRoot 等 React 内部函数调用,对比总耗时与纯脚本执行时间,确定渲染是否占用了大量帧时间。

  1. 确认不必要重渲染

在可疑组件内添加临时 console.count('组件名 render'),观察一次操作触发的渲染次数是否远多于预期。或者使用 why-did-you-render 库自动检测不必要的重新渲染。

常见原因与优化手段

  • 父组件频繁更新导致子组件被动渲染

✅ 使用 React.memo 包裹子组件,仅在 Props 变化时才重新渲染。
✅ 将耗子的“内容”提升为独立的子组件,避免父组件状态变化影响整个子树。

  • Context 值频繁变化导致大范围渲染

✅ 拆分 Context,将不同维度的状态放入独立 Context,只让相关消费者重渲染。
✅ 使用 useMemo 稳定 Context 值,避免每次渲染都创建新对象。

  • 列表数据未做 key 优化或使用匿名函数作为 Props

✅ 为列表项提供稳定且唯一的 key(不要用 index 作为 key,除非列表不会变化)。
✅ 用 useCallback 包裹传递给子组件的回调,避免每次渲染生成新函数引用破坏 React.memo

  • 大型列表一次性渲染大量 DOM

✅ 引入虚拟列表库如 react-windowreact-virtualized,只渲染可视区域的 DOM 节点。

实用示例:减少不必要重渲染

const ExpensiveChild = React.memo(({ onClick }) => {
  console.count('ExpensiveChild render');
  return <button onClick={onClick}>click</button>;
});

function Parent() {
  const [count, setCount] = useState(0);
  // 没有 useCallback 会每次创建新函数,即使 React.memo 也会重新渲染
  const handleClick = useCallback(() => setCount(c => c + 1), []);
  return <ExpensiveChild onClick={handleClick} />;
}

22.3.2 计算瓶颈:函数组件内的昂贵计算拖累渲染

典型表现

  • 渲染期间执行复杂数据处理(大数组排序、递归计算、大量正则匹配),导致帧时间飙升。
  • 输入框每键入一个字符都触发整体网格重新计算,明显卡顿。
  • 性能面板显示某组件渲染耗时较长,但不涉及大量 DOM 操作,主要是 JavaScript 运算。

排查方法

  1. 在组件函数顶部添加性能标记

使用 performance.now()console.time 包裹怀疑的代码块,测量执行时间。

  1. React Profiler 的组件耗时分析

如果组件单次渲染超过 1-2ms 且内部包含无依赖的计算逻辑,就值得优化。

常见原因与优化手段

  • 在组件函数体直接执行昂贵运算

✅ 使用 useMemo 缓存计算结果,只在依赖变化时重新计算。

  • 数据未做分片或懒处理

✅ 对于超大数据集,使用 Web Worker 将处理任务移到主线程外。
✅ 结合虚拟列表,仅渲染可见部分,计算也按需进行。

  • 复杂状态派生逻辑重复执行

✅ 用 useMemouseDerivedState 方式避免重复推导。例如过滤列表、计算统计数据等,不要每次渲染都全量计算。

实用示例:useMemo 避免重复计算

function ProductList({ products, keyword }) {
  // ❌ 每次渲染都重新过滤整个列表
  // const filtered = products.filter(p => p.name.includes(keyword));

  // ✅ 只在 products 或 keyword 变化时重新过滤
  const filtered = useMemo(
    () => products.filter(p => p.name.includes(keyword)),
    [products, keyword]
  );

  return filtered.map(p => <ProductCard key={p.id} product={p} />);
}

注意useMemo 本身也有开销,不要为了缓存而缓存。只对确实耗时的计算使用,轻量级计算(如加减乘除、简单拼接)直接写在渲染函数内即可。

22.3.3 网络瓶颈:资源加载和数据请求拖慢体验

典型表现

  • 首屏空白时间超过 3 秒,Lighthouse 性能评分很低。
  • 路由切换时长时间白屏等待,不是页面渲染慢,而是代码/js 文件加载慢。
  • 接口返回慢导致重要内容迟迟不出现,用户看到空白或大量骨架屏。

排查方法

  1. 浏览器 Network 面板
  • 检查 JS 包体积(过滤 .js),查看未压缩大小和压缩后大小,判断是否存在巨型包。
  • 查看关键请求的耗时(Content Download)和队列情况,定位网络延迟或带宽问题。
  • 检查 XHR/Fetch 请求,找到耗时久的 API 并进行后端或缓存优化。
  1. Chrome Lighthouse / PageSpeed Insights

生成报告,获取具体的优化建议,如未压缩的文本、未使用 CDN、未做代码拆分等。

  1. React DevTools Profiler 并结合网络时间轴

对比网络完成的时刻与 React 开始渲染的时刻,确认加载顺序是否阻塞了渲染。

常见原因与优化手段

  • JavaScript 包体积过大

✅ 路由级代码分割:使用 React.lazy + Suspense 对路由页面进行懒加载。
✅ 组件级懒加载:将不立即需要的部分(如弹窗、重型图表库)动态导入。
✅ Tree Shaking 清理未使用的导出,使用 ESM 格式库。
✅ 依赖分析:用 vite-plugin-visualizerwebpack-bundle-analyzer 查看大小,剔除重型替代库。

  • 数据请求阻塞首屏核心内容

✅ 尽早发起关键请求(如在 createRoot 之前预请求),或使用服务器端预取(SSR/SSG)。
✅ 使用 Suspense + React.lazy 或 TanStack Query 的惰性请求,允许先渲染外壳再请求数据。
✅ 对非首屏关键接口延迟加载,使用 IntersectionObserver 懒加载组件数据。
✅ 实现数据缓存(TanStack Query staleTime、SWR revalidateOnFocus)减少重复请求。

  • 资源加载阻塞渲染

✅ 对图片/视频使用 loading="lazy" 属性。
✅ 预加载关键字体和 CSS(<link rel="preload">)。
✅ 使用 <link rel="prefetch"> 预取下一页的 JS 代码,减少路由切换时的等待。

实用示例:路由懒加载

import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const HomePage = lazy(() => import('./pages/Home'));
const AnalyticsPage = lazy(() => import('./pages/Analytics'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>加载中...</div>}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/analytics" element={<AnalyticsPage />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

这样 AnalyticsPage 及其依赖的重型图表库只会在用户访问时才下载。

22.3.4 组合排查思路

实际场景中,三种瓶颈往往相互交织。可以按照以下步骤快速定位:

  1. 首屏加载慢 → 优先查网络瓶颈(包体积、请求数量、CDN)
  2. 交互后卡顿 → 优先查渲染瓶颈(不必要的重渲染、长列表)
  3. 交互期间高 CPU 消耗,但 DOM 变化不大 → 优先查计算瓶颈(缺少 useMemo、大数据处理)

使用 React DevTools 的 ⚡ Highlight updates when components render 选项,可视化哪些组件发生了重渲染,结合火焰图快速定位热点,再针对性优化。