Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Performance Optimization

React.memo

Ngăn component re-render khi props không thay đổi (shallow comparison).

import { memo, useState } from 'react';

interface ProductCardProps {
  id: number;
  name: string;
  price: number;
  onAddToCart: (id: number) => void;
}

// ✅ Wrap component với memo
const ProductCard = memo(function ProductCard({
  id,
  name,
  price,
  onAddToCart,
}: ProductCardProps) {
  console.log(`Rendering ProductCard ${id}`);

  return (
    <div className="product-card">
      <h3>{name}</h3>
      <p>${price}</p>
      <button onClick={() => onAddToCart(id)}>Add to Cart</button>
    </div>
  );
});

// Khi nào memo có tác dụng?
function App() {
  const [cartCount, setCartCount] = useState(0);
  const [products] = useState([...]);

  // ❌ Handler mới mỗi render → memo vô tác dụng!
  // const handleAddToCart = (id) => setCartCount(c => c + 1);

  // ✅ Stable reference với useCallback
  const handleAddToCart = useCallback((id: number) => {
    setCartCount((c) => c + 1);
    addToCart(id);
  }, []);

  return (
    <div>
      <p>Cart: {cartCount}</p>
      {products.map((p) => (
        <ProductCard
          key={p.id}
          {...p}
          onAddToCart={handleAddToCart}
        />
      ))}
    </div>
  );
}

// Custom comparison function
const MemoizedList = memo(
  function HeavyList({ items, searchQuery }) { ... },
  (prevProps, nextProps) => {
    // true = không re-render, false = re-render
    return (
      prevProps.searchQuery === nextProps.searchQuery &&
      prevProps.items.length === nextProps.items.length
    );
  }
);

Code Splitting & Lazy Loading

Chia nhỏ bundle, chỉ load code khi cần.

import { lazy, Suspense, startTransition } from 'react';

// Lazy load pages (route-based splitting)
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Analytics = lazy(() => import('./pages/Analytics'));
const Settings = lazy(() => import('./pages/Settings'));

// Lazy load heavy components
const RichTextEditor = lazy(() => import('./components/RichTextEditor'));
const DataGrid = lazy(() =>
  import('./components/DataGrid').then((module) => ({
    default: module.DataGrid, // Named export
  }))
);

function App() {
  const [showEditor, setShowEditor] = useState(false);

  return (
    <Suspense fallback={<div className="page-spinner">Loading...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/analytics" element={<Analytics />} />
      </Routes>

      <button
        onClick={() => startTransition(() => setShowEditor(true))}
      >
        Open Editor
      </button>

      {showEditor && (
        <Suspense fallback={<div>Loading editor...</div>}>
          <RichTextEditor />
        </Suspense>
      )}
    </Suspense>
  );
}

Virtualization - Render Danh sách Lớn

Chỉ render các items hiển thị trong viewport.

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

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

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 60, // Estimated row height
    overscan: 5,            // Render thêm items ngoài viewport
  });

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

Tránh Re-renders Không Cần Thiết

// 1. State colocation - đặt state gần nơi dùng
// ❌ State ở root khiến toàn bộ app re-render
function App() {
  const [inputValue, setInputValue] = useState(''); // Không cần ở đây
  return (
    <>
      <SearchBar value={inputValue} onChange={setInputValue} />
      <HeavyComponent />  {/* Re-render mỗi khi type */}
    </>
  );
}

// ✅ State trong component cần nó
function App() {
  return (
    <>
      <SearchBar /> {/* Tự quản lý state */}
      <HeavyComponent /> {/* Không re-render */}
    </>
  );
}

// 2. Children trick - tránh re-render khi parent thay đổi
function SlowParent({ children }) {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount((c) => c + 1)}>
        Count: {count}
      </button>
      {children} {/* children không re-render khi count thay đổi */}
    </div>
  );
}

function App() {
  return (
    <SlowParent>
      <HeavyComponent /> {/* Không bị ảnh hưởng bởi SlowParent state */}
    </SlowParent>
  );
}

// 3. Tách components nhỏ
// ❌ Cả form re-render khi count thay đổi
function ProductPage() {
  const [count, setCount] = useState(0);
  const [formData, setFormData] = useState({...});

  return (
    <div>
      <Counter count={count} onChange={setCount} />
      <HeavyForm formData={formData} onChange={setFormData} />
    </div>
  );
}

React Profiler

import { Profiler, ProfilerOnRenderCallback } from 'react';

const onRender: ProfilerOnRenderCallback = (
  id,           // Component tree identifier
  phase,        // "mount" hoặc "update"
  actualDuration, // Thời gian render (ms)
  baseDuration,   // Ước tính không có memo
  startTime,
  commitTime
) => {
  if (actualDuration > 16) { // Slower than 60fps
    console.warn(`Slow render in ${id}: ${actualDuration.toFixed(2)}ms`);
  }
};

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

Web Workers cho Tính toán Nặng

// worker.ts
self.onmessage = (e: MessageEvent) => {
  const { data } = e;
  const result = heavyCalculation(data);
  self.postMessage(result);
};

// Component
function DataAnalysis({ rawData }) {
  const [result, setResult] = useState(null);

  useEffect(() => {
    const worker = new Worker(
      new URL('./worker.ts', import.meta.url),
      { type: 'module' }
    );

    worker.postMessage(rawData);
    worker.onmessage = (e) => {
      setResult(e.data);
      worker.terminate();
    };

    return () => worker.terminate();
  }, [rawData]);

  return result ? <Chart data={result} /> : <Spinner />;
}

Checklist Performance

□ Dùng React DevTools Profiler để identify bottlenecks
□ Áp dụng React.memo cho components render nhiều
□ useCallback cho callbacks truyền vào memo components
□ useMemo cho tính toán nặng
□ Route-based code splitting với lazy()
□ Virtualize danh sách dài (>100 items)
□ Tránh tạo object/array mới trong JSX (gây re-render)
□ Đặt state gần nơi sử dụng (state colocation)
□ Dùng Suspense boundaries hợp lý
□ Optimize images (lazy loading, correct size)
□ Enable gzip/brotli compression ở server