前言

React 应用的性能优化是每个前端开发者都需要掌握的核心技能。随着应用复杂度的增加,性能问题会逐渐显现。本文将从理论和实践两个维度,全面介绍 React 性能优化的各种技巧。

React 渲染机制回顾

虚拟 DOM 的工作原理

React 使用虚拟 DOM 来高效更新真实 DOM,但这个过程中仍有可能出现性能瓶颈:

  1. 组件重新渲染:当 state 或 props 改变时
  2. Diff 算法:比较新旧虚拟 DOM 树
  3. 批量更新:React 18 的自动批处理机制
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 理解 React 的渲染触发
function MyComponent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');

  // 每次 count 或 name 改变都会触发重新渲染
  console.log('组件重新渲染');

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>
        点击次数: {count}
      </button>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
    </div>
  );
}

组件级优化

1. React.memo 优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ❌ 没有优化:每次父组件渲染都会重新创建
const ExpensiveComponent = ({ data }) => {
  const processedData = useMemo(() => {
    // 耗时的数据处理
    return data.map(item => ({
      ...item,
      processed: heavyProcessing(item)
    }));
  }, [data]);

  return <div>{/* 渲染处理后的数据 */}</div>;
};

// ✅ 使用 React.memo 优化
const OptimizedExpensiveComponent = React.memo(({ data }) => {
  const processedData = useMemo(() => {
    return data.map(item => ({
      ...item,
      processed: heavyProcessing(item)
    }));
  }, [data]);

  return <div>{/* 渲染处理后的数据 */}</div>;
}, (prevProps, nextProps) => {
  // 自定义比较函数
  return prevProps.data.length === nextProps.data.length &&
         prevProps.data.every((item, index) =>
           item.id === nextProps.data[index].id
         );
});

2. useMemo 和 useCallback

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function ParentComponent() {
  const [count, setCount] = useState(0);
  const [items, setItems] = useState([]);

  // ✅ 使用 useMemo 缓存计算结果
  const expensiveValue = useMemo(() => {
    console.log('执行昂贵计算...');
    return items.reduce((sum, item) => sum + item.value, 0);
  }, [items]);

  // ✅ 使用 useCallback 缓存函数
  const handleItemClick = useCallback((itemId) => {
    setItems(prevItems =>
      prevItems.map(item =>
        item.id === itemId
          ? { ...item, clicked: !item.clicked }
          : item
      )
    );
  }, []); // 空依赖数组,函数永远不会改变

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>
        Count: {count}
      </button>
      <ChildComponent
        total={expensiveValue}
        onItemClick={handleItemClick}
      />
    </div>
  );
}

const ChildComponent = React.memo(({ total, onItemClick }) => {
  console.log('子组件重新渲染');
  return (
    <div>
      <p>总计: {total}</p>
      {/* 渲染项目列表 */}
    </div>
  );
});

3. 组件拆分和懒加载

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// ✅ 将大组件拆分成小组件
const UserProfile = ({ user }) => (
  <div>
    <Avatar src={user.avatar} />
    <UserInfo name={user.name} email={user.email} />
    <UserStats stats={user.stats} />
  </div>
);

// 使用 React.lazy 进行代码分割
const LazyChart = React.lazy(() => import('./Chart'));

function Dashboard() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <h1>仪表板</h1>

      {/* 其他内容 */}

      {showChart && (
        <React.Suspense fallback={<div>加载图表...</div>}>
          <LazyChart data={chartData} />
        </React.Suspense>
      )}

      <button onClick={() => setShowChart(true)}>
        显示图表
      </button>
    </div>
  );
}

状态管理优化

1. Context 优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// ❌ 避免将频繁变化的状态放在 Context 中
const BadContext = createContext();

function BadProvider({ children }) {
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handleMouseMove = (e) => {
      setMousePosition({ x: e.clientX, y: e.clientY });
    };
    window.addEventListener('mousemove', handleMouseMove);
    return () => window.removeEventListener('mousemove', handleMouseMove);
  }, []);

  return (
    <BadContext.Provider value={mousePosition}>
      {children}
    </BadContext.Provider>
  );
}

// ✅ 使用分离的 Context
const UserContext = createContext();
const ThemeContext = createContext();

function GoodProvider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');

  // 将状态分离到不同的 Context 中
  return (
    <UserContext.Provider value={{ user, setUser }}>
      <ThemeContext.Provider value={{ theme, setTheme }}>
        {children}
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

// 使用自定义 Hook 来消费 Context
function useUser() {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('useUser must be used within UserProvider');
  }
  return context;
}

2. 状态归一化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// ❌ 嵌套状态难以维护
const [badState, setBadState] = useState({
  users: {
    '1': {
      id: '1',
      name: 'John',
      posts: [
        { id: '1', title: 'Hello', comments: [] },
        { id: '2', title: 'World', comments: [] }
      ]
    }
  }
});

// ✅ 归一化状态结构
const [goodState, setGoodState] = useState({
  users: {
    '1': { id: '1', name: 'John', postIds: ['1', '2'] }
  },
  posts: {
    '1': { id: '1', title: 'Hello', userId: '1', commentIds: [] },
    '2': { id: '2', title: 'World', userId: '1', commentIds: [] }
  },
  comments: {}
});

// 使用 Immer 简化状态更新
import { produce } from 'immer';

const addUserPost = (userId, post) => {
  setGoodState(prevState =>
    produce(prevState, draft => {
      draft.posts[post.id] = post;
      draft.users[userId].postIds.push(post.id);
    })
  );
};

渲染优化

1. 虚拟列表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 使用 react-window 实现虚拟列表
import { FixedSizeList as List } from 'react-window';

const VirtualizedList = ({ items }) => {
  const Row = ({ index, style }) => (
    <div style={style}>
      <ListItem item={items[index]} />
    </div>
  );

  return (
    <List
      height={600}
      itemCount={items.length}
      itemSize={80}
      width="100%"
    >
      {Row}
    </List>
  );
};

// 或者自己实现简单的虚拟列表
function SimpleVirtualList({ items, itemHeight, containerHeight }) {
  const [scrollTop, setScrollTop] = useState(0);

  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(
    startIndex + Math.ceil(containerHeight / itemHeight) + 1,
    items.length - 1
  );

  const visibleItems = items.slice(startIndex, endIndex + 1);

  return (
    <div
      style={{ height: containerHeight, overflow: 'auto' }}
      onScroll={(e) => setScrollTop(e.target.scrollTop)}
    >
      <div style={{ height: items.length * itemHeight, position: 'relative' }}>
        {visibleItems.map((item, index) => (
          <div
            key={startIndex + index}
            style={{
              position: 'absolute',
              top: (startIndex + index) * itemHeight,
              height: itemHeight,
              width: '100%'
            }}
          >
            <ListItem item={item} />
          </div>
        ))}
      </div>
    </div>
  );
}

2. 图片优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 图片懒加载组件
const LazyImage = ({ src, alt, placeholder, ...props }) => {
  const [isLoaded, setIsLoaded] = useState(false);
  const [isInView, setIsInView] = useState(false);
  const imgRef = useRef();

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsInView(true);
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return (
    <div ref={imgRef} {...props}>
      {isInView && (
        <img
          src={src}
          alt={alt}
          onLoad={() => setIsLoaded(true)}
          style={{
            opacity: isLoaded ? 1 : 0,
            transition: 'opacity 0.3s ease'
          }}
        />
      )}
      {!isLoaded && placeholder && (
        <div>{placeholder}</div>
      )}
    </div>
  );
};

// 响应式图片组件
const ResponsiveImage = ({ sources, alt, ...props }) => {
  return (
    <picture>
      {sources.map((source, index) => (
        <source
          key={index}
          srcSet={source.srcSet}
          media={source.media}
          type={source.type}
        />
      ))}
      <img
        src={sources[sources.length - 1].src}
        alt={alt}
        loading="lazy"
        {...props}
      />
    </picture>
  );
};

性能监控和调试

1. React DevTools Profiler

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 使用 Profiler API 进行性能监控
import { Profiler } from 'react';

const onRenderCallback = (id, phase, actualDuration) => {
  console.log('组件渲染性能:', {
    id,
    phase, // 'mount' or 'update'
    actualDuration // 实际渲染时间
  });
};

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MyComponent />
    </Profiler>
  );
}

2. 自定义性能 Hook

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 性能监控 Hook
function useRenderCounter(componentName) {
  const renderCount = useRef(0);

  useEffect(() => {
    renderCount.current += 1;
    console.log(`${componentName} 渲染次数:`, renderCount.current);
  });

  return renderCount.current;
}

// 使用示例
function MyComponent() {
  const renderCount = useRenderCounter('MyComponent');

  return <div>渲染次数: {renderCount}</div>;
}

// 性能时间测量 Hook
function usePerformanceLog(name, deps) {
  useEffect(() => {
    const startTime = performance.now();

    return () => {
      const endTime = performance.now();
      console.log(`${name} 耗时:`, endTime - startTime, 'ms');
    };
  }, deps);
}

最佳实践总结

1. 组件设计原则

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ✅ 单一职责原则
const UserAvatar = ({ user, size }) => (
  <img
    src={user.avatar}
    alt={user.name}
    width={size}
    height={size}
  />
);

const UserInfo = ({ user }) => (
  <div>
    <h3>{user.name}</h3>
    <p>{user.email}</p>
  </div>
);

// ✅ 组合而非继承
const Card = ({ children, className, ...props }) => (
  <div className={`card ${className}`} {...props}>
    {children}
  </div>
);

const UserCard = ({ user }) => (
  <Card>
    <UserAvatar user={user} size={64} />
    <UserInfo user={user} />
  </Card>
);

2. 性能优化检查清单

  • 组件是否过度渲染?
  • 是否正确使用 React.memo?
  • 是否需要使用 useMemo/useCallback?
  • 状态结构是否合理?
  • 是否使用了代码分割?
  • 图片和资源是否优化?
  • 是否有内存泄漏?

3. 持续优化策略

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 性能预算设置
const PERFORMANCE_BUDGET = {
  firstContentfulPaint: 1.5, // 秒
  largestContentfulPaint: 2.5,
  firstInputDelay: 100, // 毫秒
  cumulativeLayoutShift: 0.1
};

// 性能监控工具
function usePerformanceMonitoring() {
  useEffect(() => {
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          if (entry.entryType === 'largest-contentful-paint') {
            console.log('LCP:', entry.startTime);
          }
        }
      });

      observer.observe({ entryTypes: ['largest-contentful-paint'] });

      return () => observer.disconnect();
    }
  }, []);
}

总结

React 性能优化是一个持续的过程,需要从多个维度考虑:

  1. 组件层面:合理使用 memo、useMemo、useCallback
  2. 状态管理:优化 Context 使用,归一化状态结构
  3. 渲染优化:虚拟列表、图片懒加载、代码分割
  4. 监控调试:使用 Profiler 和性能工具持续优化

记住,过早优化是万恶之源。先确保代码正确性,再在有性能瓶颈的地方进行针对性优化。


相关工具推荐: