💡 HOC
- Render props
- Hook
經過了 9 天的考驗與試煉 (?),我們終於結束了 JS 的部分!接下來會再花 7-10 天的篇幅來探討 React。
在 React 開發中,我們經常會遇到需要在多個元件間共享邏輯的情況。比如權限檢查、資料抓取、滑鼠位置追蹤等功能,如果每個元件都重複實作這些邏輯,不僅浪費時間,也不利於維護。React 中提供了三種主要的邏輯複用模式:HOC(高階元件)、Render Props 和 Hooks。
HOC 是 Higher-Order Component 的縮寫,指的是一個函式,接收一個元件作為參數,並回傳一個新的增強元件。這個概念類似於函數式程式設計中的高階函數,只是包裝的對象是 React 元件。
簡單公式:
const EnhancedComponent = withSomething(OriginalComponent);
forwardRef
處理Render Props 是一種設計模式,元件接收一個函式型的 prop(通常命名為 render
或使用 children
),這個函式會在元件內部被呼叫,並接收元件的狀態或邏輯結果,由呼叫者決定如何渲染 UI。
React Hooks(16.8 版推出)提供了一種更直接的方式來複用邏輯:
這種差異帶來了顯著的優勢:無額外嵌套、更好的型別安全、更簡潔的組合性。
import React from "react";
// 自訂 Hook:權限檢查
function useAuthorization(userRole, allowedRoles) {
return allowedRoles.includes(userRole);
}
// 受保護的元件
function AdminDashboard() {
return <h2>歡迎來到管理員後台</h2>;
}
// 使用 Hook 的 App
export default function App() {
const userRole = "user"; // 可以來自登入狀態或 props
const isAuthorized = useAuthorization(userRole, ["admin"]);
return (
<div>
<h1>Hooks 權限控制範例</h1>
{isAuthorized ? (
<AdminDashboard />
) : (
<div>⛔ 沒有權限訪問此內容</div>
)}
</div>
);
}
import React, { useState, useEffect } from "react";
// 自訂 Hook:滑鼠位置
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
function handleMouseMove(e) {
setPosition({ x: e.clientX, y: e.clientY });
}
window.addEventListener("mousemove", handleMouseMove);
return () => window.removeEventListener("mousemove", handleMouseMove);
}, []);
return position;
}
// 使用 Hook 的 App
export default function App() {
const { x, y } = useMousePosition();
return (
<div>
<h1>Hooks 滑鼠位置範例</h1>
<p>目前滑鼠位置:X: {x}, Y: {y}</p>
{/* 可以在同一個元件中多次使用,無嵌套問題 */}
<div
style={{
position: "absolute",
left: x - 10,
top: y - 10,
width: 20,
height: 20,
borderRadius: "50%",
backgroundColor: "blue",
pointerEvents: "none",
}}
/>
</div>
);
}
特性 | HOC | Render Props | Hooks |
---|---|---|---|
使用方式 | 函式包裝元件,返回新元件 | 透過函式型 children 傳遞資料 | 直接在元件內呼叫函式 |
UI 控制權 | 在被包裝元件內部 | 完全由呼叫方決定 | 完全由呼叫方決定 |
嵌套問題 | Wrapper Hell(多層包裝) | Function-as-Child Hell | 無嵌套問題 |
Props 處理 | 可能有命名衝突 | 無衝突風險 | 無衝突風險 |
可讀性 | 包裝層太多時變差 | 嵌套太深時變差 | 最佳 |
型別安全 | 複雜(需處理泛型) | 中等 | 最佳(類似普通函式) |
效能 | 多一層渲染 | 多一層渲染 + 函式創建 | 最佳 |
組合性 | 需要 compose 或嵌套 | 會產生多層嵌套 | 可平行呼叫多個 Hook |
Class Component 支援 | ✅ | ✅ | ❌ |
什麼時候選擇哪種模式?
一般來說,新專案的首選絕對是 hooks 因為他簡潔、高效,也是目前的主流寫法。如果遇到舊專案還在使用 Class component 的,那可能還是會使用 HOC 或 Render props。
其他考量像是純動畫、資料視覺化的專案,需要靈活 render 的場景,render props 就可以幫助達到完全控制 UI;或是有第三方 component 無法修改的情況,也可能需要使用 HOC,從外面包一層以增加需要的功能。
從 HOC 遷移到 Hooks
從 Render Props 遷移到 Hooks
use
開頭// 不好:每次都創建新函式
<MouseTracker>
{(pos) => <div>{pos.x}</div>}
</MouseTracker>
// 好:使用 useCallback
const renderPosition = useCallback((pos) => <div>{pos.x}</div>, []);
<MouseTracker>{renderPosition}</MouseTracker>
const ExpensiveComponent = React.memo(({ data }) => {
return <div>{/* 複雜的渲染邏輯 */}</div>;
});