正在加载,请稍候…

React Performance Optimization: Memoization, Virtualization, and Code Splitting

Master React performance with useMemo, useCallback, React.memo, virtual lists, lazy loading, Suspense, useTransition, and profiling with React DevTools.

React Performance Optimization

React is fast by default, but complex applications suffer from unnecessary re-renders and large bundle sizes. This guide covers systematic performance optimization.

useMemo and useCallback

function ProductList({ products, onSelect }: Props) {
  const [filter, setFilter] = useState('');

  // Only recomputes when dependencies change
  const filtered = useMemo(() => {
    return products
      .filter(p => p.name.toLowerCase().includes(filter.toLowerCase()))
      .sort((a, b) => a.name > b.name ? 1 : -1);
  }, [products, filter]);

  // Stable reference for memoized children
  const handleSelect = useCallback((id: string) => {
    onSelect(id);
  }, [onSelect]);

  return (
    <div>
      <input value={filter} onChange={e => setFilter(e.target.value)} />
      {filtered.map(product => (
        <ProductCard 
          key={product.id}
          product={product}
          onSelect={handleSelect}
        />
      ))}
    </div>
  );
}

const ProductCard = React.memo(({ product, onSelect }: Props) => {
  return <div onClick={() => onSelect(product.id)}>{product.name}</div>;
});

Virtual Lists for Large Data

import { FixedSizeList } from 'react-window';

const Row = ({ index, style, data }: any) => {
  const item = data.items[index];
  return (
    <div style={style} onClick={() => data.onSelect(item.id)}>
      {item.name} - ${item.price}
    </div>
  );
};

function VirtualProductList({ products, onSelect }: Props) {
  const itemData = useMemo(
    () => ({ items: products, onSelect }),
    [products, onSelect]
  );
  
  return (
    <FixedSizeList
      height={600}
      itemCount={products.length}
      itemSize={60}
      itemData={itemData}
    >
      {Row}
    </FixedSizeList>
  );
}

Code Splitting and Lazy Loading

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

function App() {
  return (
    <Suspense fallback={<PageSkeleton />}>
      <Routes>
        <Route path="/" element={<Dashboard />} />
        <Route path="/analytics" element={<Analytics />} />
      </Routes>
    </Suspense>
  );
}

useTransition and useDeferredValue

function SearchPage() {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    setQuery(value); // Urgent: update input immediately
    
    startTransition(() => {
      // Non-urgent: can be interrupted if user types again
      setSearchResults(performSearch(value));
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending ? <Spinner /> : <Results />}
    </div>
  );
}

// useDeferredValue for expensive renders
function SearchResults({ query }: { query: string }) {
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  
  const results = useMemo(
    () => performExpensiveSearch(deferredQuery),
    [deferredQuery]
  );
  
  return (
    <div style={{ opacity: isStale ? 0.7 : 1 }}>
      {results.map(r => <ResultItem key={r.id} result={r} />)}
    </div>
  );
}

Context Optimization

// BAD: Single context causes all consumers to re-render
const AppContext = createContext({ user: null, cart: [], theme: 'light' });

// GOOD: Split contexts by update frequency
const UserContext = createContext<User | null>(null);
const CartContext = createContext<Cart>({ items: [], total: 0 });
const ThemeContext = createContext<'light' | 'dark'>('light');

Performance Checklist

  • Profile with React DevTools Profiler first
  • Add React.memo to components with stable props
  • Use useMemo for expensive computations
  • Use useCallback for handlers passed to memoized children
  • Virtualize lists with 100+ items
  • Code split routes and heavy components
  • Use useTransition for non-urgent updates
  • Avoid inline objects/arrays in JSX props

Summary

Profile first, then optimize. Over-memoization adds complexity without benefit. React's built-in tools (useTransition, useDeferredValue) handle concurrent rendering gracefully.