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

第 6 課:React 模式與效能

高階元件、複合元件、渲染道具。 React.memo、useMemo、useCallback 最佳化。 React DevTools Profiler,效能反模式。

💻 程式設計 — 第 6 課 第 6 課:React 模式與效能

React 和 Next.js:從基礎到高級

第 2 部分:React 進階和狀態管理

亞洲開發網

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 → Profiler 選項卡
  • 點擊“錄製”→“互動”→“停止”
  • 查看火焰圖:哪些元件渲染時間最長
  • 請參閱“為什麼要渲染?”對於每個組件
  • 突出顯示更新:設定→反白更新→查看哪些元件重新渲染

下一篇: 樣式和 UI 元件 — Tailwind CSS、shadcn/ui 和 Framer Motion 動畫。