跳到主要内容

React 性能优化

本章将介绍 React 应用中常见的性能问题及其优化方法,特别是如何利用 React 18+ 的新特性来提升用户体验。

性能优化核心思路

  1. 减少渲染次数:避免不必要的组件更新。
  2. 减小渲染压力:将耗时任务拆分或延迟。
  3. 缓存计算结果:避免重复执行昂贵的计算逻辑。

1. 避免不必要的重新渲染

React.memo

React.memo 是一个高阶组件,它会对组件的 props 进行浅比较。如果 props 没有变化,React 将跳过渲染该组件。

实时编辑器
const Child = React.memo(({ name }) => {
  console.log("子组件渲染了");
  return <div style={{ padding: '10px', border: '1px solid blue' }}>你好, {name}</div>;
});

function Parent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("张三");

  return (
    <div>
      <p>父组件计数值: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>增加计数(不会触发子组件重绘)</button>
      <button onClick={() => setName(n => n === "张三" ? "李四" : "张三")} style={{ marginLeft: '10px' }}>
        修改名称(会触发子组件重绘)
      </button>
      <hr />
      <Child name={name} />
    </div>
  );
}
结果
Loading...

2. 缓存计算与回调

useMemo 和 useCallback

  • useMemo:缓存计算结果
  • useCallback:缓存函数定义
实时编辑器
function ExpensiveCalculation() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");

  // 模拟昂贵的计算
  const expensiveValue = useMemo(() => {
    console.log("执行昂贵计算...");
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += i;
    }
    return result + count;
  }, [count]); // 仅在 count 变化时重新计算

  return (
    <div>
      <h3>结果: {expensiveValue}</h3>
      <button onClick={() => setCount(c => c + 1)}>更新计数 (触发重新计算)</button>
      <input 
        value={text} 
        onChange={(e) => setText(e.target.value)} 
        placeholder="输入内容不触发昂贵计算" 
        style={{ marginLeft: '10px' }}
      />
    </div>
  );
}
结果
Loading...

3. 并发渲染 (React 18+)

React 18 引入了并发特性,允许我们将更新标记为不同的优先级。

useTransition

用于将紧急更新(如输入文字)与非紧急更新(如过滤列表)分离。

实时编辑器
function FilterList() {
  const [query, setQuery] = useState("");
  const [list, setList] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value); // 紧急更新:立即反映输入

    // 非紧急更新:在后台处理列表过滤
    startTransition(() => {
      const items = Array.from({ length: 5000 }, (_, i) => `${value} 项目 ${i}`);
      setList(items);
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} placeholder="键入文字查看并发渲染..." />
      {isPending ? <p>正在计算过滤结果...</p> : (
        <ul style={{ maxHeight: '200px', overflow: 'auto' }}>
          {list.slice(0, 100).map((item, i) => <li key={i}>{item}</li>)}
        </ul>
      )}
    </div>
  );
}
结果
Loading...

4. 代码分割

lazy 和 Suspense

通过动态导入组件,减少首屏加载的包体积。

import { lazy, Suspense } from 'react';

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

function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<HeavyComponent />
</Suspense>
);
}

5. 虚拟列表

当渲染包含数千个条目的列表时,使用虚拟化技术(只渲染可见区域的 DOM)。推荐库:

  • react-window
  • @tanstack/react-virtual

小结

  1. React.memo:跳过未变化的组件渲染。
  2. useMemo/useCallback:缓存昂贵逻辑和函数引用。
  3. useTransition:优化长列表过滤或大数据更新的交互感。
  4. Suspense:实现优雅的异步加载和代码分割。

练习

  1. 使用 React.memo 优化一个复杂的表单行组件。
  2. 给一个搜索框添加 useTransition,观察在大数据列表下的输入流畅度提升。
  3. 使用 useMemo 优化一个基于多个过滤条件的商品列表。

常见性能问题及解决方案

问题症状解决方案
不必要的重新渲染组件频繁渲染React.memo、useMemo、useCallback
大列表渲染慢滚动卡顿虚拟列表、分页、懒加载
重复计算CPU 占用高useMemo
重复创建函数内存占用高useCallback
大 Bundle首屏加载慢代码分割、懒加载
内存泄漏内存持续增长清理 useEffect、取消订阅

性能检查清单

  1. ✅ 使用 React DevTools Profiler 分析
  2. ✅ 检查不必要的重新渲染
  3. ✅ 使用 React.memo 包装纯组件
  4. ✅ 使用 useMemo 缓存计算结果
  5. ✅ 使用 useCallback 缓存回调函数
  6. ✅ 使用虚拟列表处理大列表
  7. ✅ 实现代码分割和懒加载
  8. ✅ 生产环境构建并优化
  9. ✅ 清理副作用和取消订阅
  10. ✅ 监控性能指标

小结

  1. 避免不必要的重新渲染:React.memo、useMemo、useCallback
  2. 代码分割:React.lazy、Suspense
  3. 列表优化:虚拟列表、分页
  4. Context 优化:分离 Context、useMemo
  5. 防抖节流:减少高频事件触发
  6. 工具:React DevTools Profiler、webpack-bundle-analyzer

练习

  1. 创建一个列表组件,渲染 1000 条数据,并使用虚拟列表优化
  2. 实现一个搜索组件,使用防抖延迟搜索请求
  3. 创建一个表单组件,使用 useMemo 缓存验证逻辑
  4. 实现代码分割,只在需要时加载组件