说说 React 性能优化的手段有哪些?

一、是什么

React 性能优化是指通过各种技术手段减少不必要的计算和 DOM 操作,使应用保持流畅的用户体验。React 的虚拟 DOM 和 diff 算法已经提供了基本的性能保障,但在复杂应用中,仍然需要开发者有意识地进行优化。

性能优化的核心方向:

减少渲染次数 → 让不该渲染的组件跳过 render
减少渲染量级 → 让必须渲染的组件更快完成
减少加载体积 → 让用户更快看到首屏内容

二、组件记忆化

React.memo

用于函数组件,浅比较 props,props 不变时跳过重新渲染:

interface UserCardProps {
  name: string;
  avatar: string;
  role: string;
}

const UserCard = React.memo(function UserCard({ name, avatar, role }: UserCardProps) {
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <span>{role}</span>
    </div>
  );
});

useMemo — 缓存计算结果

避免每次渲染都执行昂贵的计算:

function AnalyticsTable({ data, sortKey, filter }: Props) {
  const processedData = useMemo(() => {
    const filtered = data.filter(item =>
      item.category === filter || filter === 'all'
    );
    return filtered.sort((a, b) => {
      if (a[sortKey] < b[sortKey]) return -1;
      if (a[sortKey] > b[sortKey]) return 1;
      return 0;
    });
  }, [data, sortKey, filter]);

  return (
    <table>
      <tbody>
        {processedData.map(row => (
          <tr key={row.id}>
            <td>{row.name}</td>
            <td>{row.value}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

useCallback — 缓存函数引用

配合 React.memo 防止子组件因函数引用变化而重新渲染:

function TodoApp() {
  const [todos, setTodos] = useState<Todo[]>([]);

  const addTodo = useCallback((text: string) => {
    setTodos(prev => [...prev, { id: crypto.randomUUID(), text, done: false }]);
  }, []);

  const toggleTodo = useCallback((id: string) => {
    setTodos(prev =>
      prev.map(t => t.id === id ? { ...t, done: !t.done } : t)
    );
  }, []);

  return (
    <div>
      <TodoInput onAdd={addTodo} />
      <MemoizedTodoList todos={todos} onToggle={toggleTodo} />
    </div>
  );
}

三、代码分割与懒加载

React.lazy + Suspense

按路由或组件级别进行代码分割,减少初始 bundle 体积:

import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Reports = lazy(() => import('./pages/Reports'));

function App() {
  return (
    <Suspense fallback={<PageSkeleton />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
        <Route path="/reports" element={<Reports />} />
      </Routes>
    </Suspense>
  );
}

四、列表虚拟化

对于长列表(数百或数千项),只渲染可视区域内的元素,大幅减少 DOM 节点数量:

import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }: { items: Item[] }) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 60,
    overscan: 5,
  });

  return (
    <div ref={parentRef} style={{ height: '500px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
        {virtualizer.getVirtualItems().map(virtualRow => (
          <div
            key={virtualRow.key}
            style={{
              position: 'absolute',
              top: 0,
              transform: `translateY(${virtualRow.start}px)`,
              height: `${virtualRow.size}px`,
              width: '100%',
            }}
          >
            <ItemRow item={items[virtualRow.index]} />
          </div>
        ))}
      </div>
    </div>
  );
}

虚拟化可以让 10000 条数据的列表和 20 条数据的列表保持相同的渲染性能。

五、状态管理优化

状态下放

将频繁变化的状态放在尽可能低的层级,避免不相关的组件重新渲染:

// 优化前:整个 Page 因为 hover 状态重新渲染
function Page() {
  const [hoveredId, setHoveredId] = useState<string | null>(null);
  return (
    <div>
      <Sidebar />
      <ItemList hoveredId={hoveredId} onHover={setHoveredId} />
    </div>
  );
}

// 优化后:hover 状态下放到 ItemList 内部
function Page() {
  return (
    <div>
      <Sidebar />
      <ItemList />
    </div>
  );
}

Context 拆分

避免将不同更新频率的数据放在同一个 Context 中,拆分为独立的 ThemeContext、AuthContext 等,使消费者只订阅自己关心的数据。

六、并发特性

useTransition

将低优先级更新标记为 transition,让高优先级的用户交互保持流畅:

function TabContainer() {
  const [tab, setTab] = useState('home');
  const [isPending, startTransition] = useTransition();

  const switchTab = (nextTab: string) => {
    startTransition(() => {
      setTab(nextTab);
    });
  };

  return (
    <div>
      <nav>
        {['home', 'posts', 'contacts'].map(t => (
          <button key={t} onClick={() => switchTab(t)} className={tab === t ? 'active' : ''}>
            {t}
          </button>
        ))}
      </nav>
      <div style={{ opacity: isPending ? 0.7 : 1 }}>
        <TabContent tab={tab} />
      </div>
    </div>
  );
}

useDeferredValue

延迟更新低优先级的值,适用于依赖外部传入值的场景:

function SearchResults({ query }: { query: string }) {
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  const results = useMemo(
    () => heavySearch(deferredQuery),
    [deferredQuery]
  );

  return (
    <div style={{ opacity: isStale ? 0.6 : 1 }}>
      {results.map(item => (
        <ResultItem key={item.id} item={item} />
      ))}
    </div>
  );
}

七、避免常见的性能陷阱

避免在 render 中创建新对象和函数

// 不推荐:每次渲染创建新的 style 对象和内联函数
<MemoizedCard
  style={{ padding: '16px' }}
  onClick={() => handleClick(item.id)}
/>

// 推荐:提取为模块级常量或使用 hooks 缓存
const cardStyle = { padding: '16px' };

function Parent() {
  const handleClick = useCallback((id: string) => { /* ... */ }, []);
  return <MemoizedCard style={cardStyle} onItemClick={handleClick} />;
}

正确使用 key

使用稳定唯一的业务 ID 而非 index,避免列表变动时组件状态错乱和不必要的 DOM 操作。

防抖与节流

对搜索输入、滚动、窗口 resize 等高频事件使用 debounce/throttle,减少触发渲染和网络请求的频率。

八、性能分析工具

使用 React DevTools Profiler 录制渲染过程,定位渲染耗时超过 16ms 的组件。配合 <Profiler> 组件可以在代码中收集渲染指标:

import { Profiler } from 'react';

function App() {
  return (
    <Profiler id="App" onRender={(id, phase, duration) => {
      if (duration > 16) console.warn(`${id} 渲染耗时 ${duration.toFixed(2)}ms`);
    }}>
      <MainContent />
    </Profiler>
  );
}
指标目标工具
更新渲染< 16ms(60fps)React Profiler
JS Bundle< 200KB (gzipped)webpack-bundle-analyzer
LCP< 3sLighthouse

十、React Compiler

React 19 引入的 React Compiler 能在编译时自动完成大部分记忆化优化:

// 开发者只需要编写简洁的代码
function ProductList({ products, category }) {
  const filtered = products.filter(p => p.category === category);

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

// React Compiler 在编译时自动分析并插入等效的 useMemo/memo 逻辑

React Compiler 遵循 React 的规则(纯函数组件、Hooks 规则等),只要代码符合这些约定,就能获得自动优化。

十一、总结

优化方向手段效果
减少渲染次数memo / useMemo / useCallback跳过无变化的组件
减少渲染范围状态下放 / Context 拆分缩小更新影响面
减少渲染量虚拟化 / 分页减少 DOM 节点数
减少加载量lazy / Suspense / 代码分割按需加载,减小包体积
提升响应性useTransition / useDeferredValue优先级调度
事件优化防抖 / 节流减少触发频率
自动优化React Compiler编译时插入记忆化

性能优化的首要原则是度量先行:使用 Profiler 定位真正的瓶颈,针对性地优化,避免为了优化而优化。过早或过度的优化会增加代码复杂性,反而不利于维护。