Chuyển đến nội dung chính

Lesson 6: React Patterns & Performance

Higher-Order Components, Compound Components, Render Props. React.memo, useMemo, useCallback optimization. React DevTools Profiler, performance anti-patterns.

💻 Programming — Lesson 6 Lesson 6: React Patterns & Performance

React & Next.js: From Basics to Advanced

Part 2: React Advanced & State Management

xdev.asia

1. React.memo

import { memo } from 'react';

// Memoize component — chỉ re-render khi props thay đổi
const ExpensiveList = memo(function ExpensiveList({ items }: { items: Item[] }) {
  console.log('ExpensiveList rendered');
  return (
    <ul>
      {items.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
});

// Custom comparison
const UserCard = memo(
  function UserCard({ user }: { user: User }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => {
    return prevProps.user.id === nextProps.user.id
      && prevProps.user.name === nextProps.user.name;
  }
);

2. Higher-Order Components (HOC)

// withAuth HOC
function withAuth<T extends object>(Component: React.ComponentType<T>) {
  return function AuthenticatedComponent(props: T) {
    const { user, isLoading } = useAuth();

    if (isLoading) return <Spinner />;
    if (!user) return <Navigate to="/login" />;

    return <Component {...props} />;
  };
}

// Usage
const ProtectedDashboard = withAuth(Dashboard);

// withLogging HOC
function withLogging<T extends object>(Component: React.ComponentType<T>, name: string) {
  return function LoggedComponent(props: T) {
    useEffect(() => {
      console.log(`${name} mounted`);
      return () => console.log(`${name} unmounted`);
    }, []);

    return <Component {...props} />;
  };
}

3. Render Props

interface MouseTrackerProps {
  render: (position: { x: number; y: number }) => React.ReactNode;
}

function MouseTracker({ render }: MouseTrackerProps) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

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

  return <div>{render(position)}</div>;
}

// Usage
<MouseTracker
  render={({ x, y }) => (
    <p>Mouse: {x}, {y}</p>
  )}
/>

4. Performance Patterns

// ❌ Anti-pattern: Object/array creation in render
function Bad() {
  return <Child style={{ color: 'red' }} items={[1, 2, 3]} />; // New ref every render!
}

// ✅ Fix: useMemo or hoist outside
const style = { color: 'red' };
const items = [1, 2, 3];

function Good() {
  return <Child style={style} items={items} />;
}

// ❌ Anti-pattern: Inline function as prop to memoized child
function Bad2() {
  return <MemoizedChild onClick={() => doSomething()} />;
}

// ✅ Fix: useCallback
function Good2() {
  const handleClick = useCallback(() => doSomething(), []);
  return <MemoizedChild onClick={handleClick} />;
}

// ❌ Anti-pattern: State too high in tree
// ✅ Fix: Lift state only as needed, or co-locate state

5. Code Splitting & Lazy Loading

import { lazy, Suspense } from 'react';

// Lazy load components
const AdminPanel = lazy(() => import('./AdminPanel'));
const Analytics = lazy(() => import('./Analytics'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/admin" element={<AdminPanel />} />
        <Route path="/analytics" element={<Analytics />} />
      </Routes>
    </Suspense>
  );
}

6. React DevTools Profiler

  • Open React DevTools → Profiler tab
  • Click "Record" → interact → "Stop"
  • See the flame graph: which components take the longest to render
  • See "Why did this render?" for each component
  • Highlight updates: Settings → Highlight updates → see which components re-render

Next article: Styling & UI Components — Tailwind CSS, shadcn/ui, and Framer Motion animations.