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

レッスン 6: パターンとパフォーマンスに反応する

高次コンポーネント、複合コンポーネント、レンダー プロップ。 React.memo、useMemo、useCallback の最適化。 React DevTools Profiler、パフォーマンス アンチパターン。

💻 プログラミング — レッスン 6 レッスン 6: パターンとパフォーマンスに反応する

React と Next.js: 基本から高度まで

パート 2: React Advanced と状態管理

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. 高次コンポーネント (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. 小道具をレンダリングする

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. 演奏パターン

// ❌ 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. コード分割と遅延読み込み

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 プロファイラー

  • React DevTools → 「プロファイラー」タブを開きます
  • 「録音」→対話→「停止」をクリックします。
  • フレーム グラフを参照してください: どのコンポーネントがレンダリングに最も時間がかかるか
  • 「なぜこれがレンダリングされたのか?」を参照してください。コンポーネントごとに
  • ハイライト更新: 設定 → 更新を強調表示 → どのコンポーネントが再レンダリングされるかを確認

次の記事: スタイリングとUIコンポーネント — Tailwind CSS、shadcn/ui、および Framer Motion アニメーション。