🚀 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 |