☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
react 進階

🚀 React 進階:效能優化與設計模式

📌 React.memo — 避免不必要的重新渲染

React.memo 是一個高階元件(HOC),當 props 沒變時跳過重新渲染。

import { memo, useState } from 'react';

// 用 React.memo 包裝,props 相同就不重新渲染
const ExpensiveList = memo(function ExpensiveList({ items, onSelect }) {
  console.log('ExpensiveList 渲染了');  // 觀察渲染次數
  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => onSelect(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
});

function App() {
  const [count, setCount] = useState(0);
  const [items] = useState([
    { id: 1, name: 'React' },
    { id: 2, name: 'Vue' },
  ]);

  // ⚠️ 如果不用 useCallback,每次渲染都建立新函式
  //    → React.memo 失效(因為 props 不同)
  const handleSelect = useCallback((id) => {
    console.log('選擇了', id);
  }, []);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>
        計數:{count}
      </button>
      {/* count 改變時,ExpensiveList 不會重新渲染 */}
      <ExpensiveList items={items} onSelect={handleSelect} />
    </div>
  );
}

🧠 虛擬 DOM Diff 算法(底層 JS 機制)

React 的效能關鍵在於它的 Reconciliation(調和) 算法。

React Diff 規則:
1. 不同類型的元素 → 整棵子樹重建
2. 同類型的元素 → 只更新改變的屬性
3. 列表元素 → 透過 key 比對(O(n) 而非 O(n³))
// 底層 JavaScript:React 如何比較虛擬 DOM
// 舊的虛擬 DOM
const oldTree = {
  type: 'div',
  props: { className: 'container' },
  children: [
    { type: 'h1', props: {}, children: ['Hello'] },
    { type: 'p',  props: {}, children: ['World'] },
  ]
};

// 新的虛擬 DOM
const newTree = {
  type: 'div',
  props: { className: 'container active' },  // className 改了
  children: [
    { type: 'h1', props: {}, children: ['Hello'] },    // 沒變
    { type: 'p',  props: {}, children: ['React!'] },   // 文字改了
  ]
};

// React 只會:
// 1. 更新 div 的 className
// 2. 更新 p 的文字內容
// 不會重建整個 DOM 樹!

⚠️ 這就是 React 不是原生 JS 的核心原因: 原生 JS 操作 DOM 你需要自己追蹤哪些部分改了。 React 透過虛擬 DOM + Diff 算法自動幫你做最小化更新。


⏳ Suspense 與 lazy loading

import { Suspense, lazy } from 'react';

// lazy 動態載入(程式碼分割)
const AdminPanel = lazy(() => import('./pages/AdminPanel'));
const UserDashboard = lazy(() => import('./pages/UserDashboard'));

function App() {
  return (
    <Suspense fallback={<div>載入中...</div>}>
      <Routes>
        <Route path="/admin" element={<AdminPanel />} />
        <Route path="/dashboard" element={<UserDashboard />} />
      </Routes>
    </Suspense>
  );
}

💡 為什麼要 lazy loading? 使用者進入首頁時不需要載入 AdminPanel 的程式碼。 lazy loading 只在實際訪問時才下載,減少初始載入時間。


🛡️ Error Boundaries — 錯誤邊界

Error Boundary 是目前唯一需要使用 class component 的場景。

import { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };     // 更新 state 顯示錯誤 UI
  }

  componentDidCatch(error, errorInfo) {
    console.error('元件錯誤:', error, errorInfo);
    // 可以送到錯誤追蹤服務(Sentry 等)
  }

  render() {
    if (this.state.hasError) {
      return (
        <div style={{ padding: '20px', background: '#ffe0e0' }}>
          <h2>發生錯誤</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false })}>
            重試
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}

// 使用
function App() {
  return (
    <ErrorBoundary>
      <Dashboard />     {/* 如果 Dashboard 出錯,顯示錯誤 UI */}
    </ErrorBoundary>
  );
}

🎨 常見設計模式

HOC(Higher-Order Component)

// withAuth HOC:包裝元件加上認證檢查
function withAuth(WrappedComponent) {
  return function AuthenticatedComponent(props) {
    const { user } = useAuth();

    if (!user) return <Navigate to="/login" />;
    return <WrappedComponent {...props} user={user} />;
  };
}

// 使用
const ProtectedDashboard = withAuth(Dashboard);

Compound Components 模式

// 像 HTML 的 <select> + <option> 一樣的元件 API
function Tabs({ children, defaultTab }) {
  const [activeTab, setActiveTab] = useState(defaultTab);

  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

Tabs.Tab = function Tab({ id, children }) {
  const { activeTab, setActiveTab } = useContext(TabsContext);
  return (
    <button
      className={activeTab === id ? 'active' : ''}
      onClick={() => setActiveTab(id)}
    >
      {children}
    </button>
  );
};

Tabs.Panel = function Panel({ id, children }) {
  const { activeTab } = useContext(TabsContext);
  return activeTab === id ? <div>{children}</div> : null;
};

// 使用 — 直覺的 API
<Tabs defaultTab="home">
  <Tabs.Tab id="home">首頁</Tabs.Tab>
  <Tabs.Tab id="settings">設定</Tabs.Tab>
  <Tabs.Panel id="home">首頁內容</Tabs.Panel>
  <Tabs.Panel id="settings">設定內容</Tabs.Panel>
</Tabs>

🌐 React Server Components(RSC)概念

React Server Components 是 React 的最新發展方向(Next.js 13+ App Router 使用)。

傳統 React(Client Components):
  伺服器 → 送 JS 到瀏覽器 → 瀏覽器執行 JS → 渲染畫面

React Server Components:
  伺服器 → 伺服器上執行元件 → 送 HTML 到瀏覽器 → 更快的首屏
// Server Component(預設)— 在伺服器執行
async function ProductPage({ id }) {
  const product = await db.products.findById(id);  // 直接查資料庫
  return <ProductDetail product={product} />;
}

// Client Component — 需要互動時使用
'use client';  // 必須加這行標記
function AddToCartButton({ productId }) {
  const [added, setAdded] = useState(false);
  return (
    <button onClick={() => setAdded(true)}>
      {added ? '已加入' : '加入購物車'}
    </button>
  );
}

✅ 本章重點

技巧 說明
React.memo 避免不必要的子元件重新渲染
虛擬 DOM Diff React 自動最小化 DOM 操作的核心機制
Suspense + lazy 程式碼分割,按需載入
Error Boundary 攔截子元件錯誤,顯示錯誤 UI
HOC / Compound 常見的元件重用模式
RSC 伺服器端渲染元件,減少客戶端 JS

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊