iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0
Modern Web

JavaScript 進階修煉與一些 React ——離開初階工程師新手村的頭30天系列 第 10

離開 JS 初階工程師新手村的 Day 10|強大組合技:HOC 與 props 傳遞

  • 分享至 

  • xImage
  •  
💡 HOC
-  Render props
-  Hook

經過了 9 天的考驗與試煉 (?),我們終於結束了 JS 的部分!接下來會再花 7-10 天的篇幅來探討 React。


在 React 開發中,我們經常會遇到需要在多個元件間共享邏輯的情況。比如權限檢查、資料抓取、滑鼠位置追蹤等功能,如果每個元件都重複實作這些邏輯,不僅浪費時間,也不利於維護。React 中提供了三種主要的邏輯複用模式:HOC(高階元件)Render PropsHooks

高階元件(HOC):包裝現有元件的藝術

HOC 是 Higher-Order Component 的縮寫,指的是一個函式,接收一個元件作為參數,並回傳一個新的增強元件。這個概念類似於函數式程式設計中的高階函數,只是包裝的對象是 React 元件。

簡單公式:

const EnhancedComponent = withSomething(OriginalComponent);

範例

https://ithelp.ithome.com.tw/upload/images/20250921/20168365peNKUoD4nE.png

HOC 的優點

  1. 元件封裝:可以在不修改原始元件的情況下增加功能
  2. 邏輯複用:相同的邏輯可以套用到多個不同的元件
  3. 向後相容:特別適合處理無法修改的第三方元件或 Class Component

HOC 的缺點

  1. Wrapper Hell:多個 HOC 層層包裝會造成元件樹難以追蹤
  2. Props 衝突:不同 HOC 可能會產生同名 props,造成覆蓋問題
  3. Ref 傳遞問題:需要額外使用 forwardRef 處理

Render Props:靈活的資料共享模式

Render Props 是一種設計模式,元件接收一個函式型的 prop(通常命名為 render 或使用 children),這個函式會在元件內部被呼叫,並接收元件的狀態或邏輯結果,由呼叫者決定如何渲染 UI。

範例:滑鼠位置追蹤

https://ithelp.ithome.com.tw/upload/images/20250921/20168365fbxOw3WZZJ.png

Render Props 的優點

  1. UI 控制權:呼叫方擁有完全的 UI 控制權,可以靈活決定如何使用資料
  2. 無 Props 衝突:基本不會有 props 命名衝突的問題
  3. 組合性強:可以在同一個元件內使用多個 Render Props

Render Props 的缺點

  1. Function-as-Child Hell:多層嵌套會讓 JSX 變得難以閱讀
  2. 效能問題:每次渲染都會創建新的函式,可能造成不必要的重新渲染

React Hooks

為什麼 Hooks 能取代 HOC 和 Render Props?

React Hooks(16.8 版推出)提供了一種更直接的方式來複用邏輯:

  • HOC / Render Props:用元件包邏輯
  • Hooks:用函式包邏輯

這種差異帶來了顯著的優勢:無額外嵌套、更好的型別安全、更簡潔的組合性。

Hooks 版本:權限控制

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>
  );
}

Hooks 版本:滑鼠位置追蹤

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
https://ithelp.ithome.com.tw/upload/images/20250921/20168365AI1i2iX21s.png

從 Render Props 遷移到 Hooks
https://ithelp.ithome.com.tw/upload/images/20250921/20168365wiUx8IT2Eu.png

最佳實踐

設計原則

  1. 單一責任:每個 HOC、Render Props 或 Hook 都應該只專注一件事
  2. 命名清晰:使用描述性的名稱,Hook 以 use 開頭
  3. 保持簡單:避免過度抽象,優先考慮可讀性

效能考量

  1. 避免在 render 中創建新函式
// 不好:每次都創建新函式
<MouseTracker>
  {(pos) => <div>{pos.x}</div>}
</MouseTracker>

// 好:使用 useCallback
const renderPosition = useCallback((pos) => <div>{pos.x}</div>, []);
<MouseTracker>{renderPosition}</MouseTracker>
  1. 適當使用 React.memo
const ExpensiveComponent = React.memo(({ data }) => {
  return <div>{/* 複雜的渲染邏輯 */}</div>;
});

結語

  • Hooks 是現代 React 開發的首選,提供了最佳的開發體驗
  • HOC 適合處理舊有的 Class Component 或第三方元件
  • Render Props 在需要高度靈活的 UI 控制時仍有其價值

上一篇
離開 JS 初階工程師新手村的 Day 09|自訂 Hook:鍛造自己的技能卷軸
系列文
JavaScript 進階修煉與一些 React ——離開初階工程師新手村的頭30天10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言