iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
Modern Web

現代函式庫與JavaScript的關係系列 第 9

Day 9 | HOC 高階組件與渲染劫持|反向繼承,以及 Vue / Angular 的對應機制

  • 分享至 

  • xImage
  •  

起因是在看《前端面試寶典之 React 篇》影片,冒出「渲染劫持」這個詞,追問下去發現它其實是 HOC 的一種實作技巧,而且是已經被 React 官方淡出的舊派做法。第二個問題問得很好:「render 又不是 React 專有的,你是不是要講三大框架?」——答案是三個框架各有自己的複用機制,名字完全不同。
關聯筆記:[[高階函式與函數式範式-取代OOP三大設計模式]] 講「函式當參數與回傳值」這個底層骨架,HOC 就是它在組件層的投影,也就是 Decorator 裝飾者模式;[[00-前端框架比較-Vue-React-Angular難易度與優缺點]] 講三框架的整體取捨,這篇聚焦在「邏輯複用」這一個切面。


重點整理

一. 三個詞先定義清楚

(a) HOC(Higher-Order Component,高階組件):一個「接收組件、回傳新組件」的函式。名字借自數學的高階函式(Higher-Order Function),差別只在處理的單位從函式變成組件。

(b) 渲染劫持(Render Hijacking):在原組件真正被畫出來之前,攔截並修改它的渲染結果。這是 HOC 的一項技巧,不是另一個獨立概念。

(c) 反向繼承(Inheritance Inversion,II):HOC 的兩種實作流派之一。另一種是屬性代理(Props Proxy,PP)。

[!note] 為什麼叫「反向」
正常繼承是「子類別繼承父類別」。這裡 HOC 是包裝者,理應是外層,卻反過來去 extends 被包裝的組件,讓包裝者變成子類別,包裝關係跟繼承關係方向相反,所以叫反向繼承。

二. 反向繼承的核心機制

(d) 關鍵:class HOC extends WrappedComponent。HOC 繼承了原組件,於是它拿得到原組件的 renderstate、生命週期方法。

(e) 在 HOC 自己的 render() 裡呼叫 super.render(),就能拿到原組件產出的 React 元素樹(Virtual DOM),接著讀取、修改或整個覆蓋掉。

function withRenderHijack(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      // super.render() 回傳原組件的 JSX 元素樹
      const elementTree = super.render();

      if (!this.props.isAllowed) {
        return <p>沒有權限</p>;      // 整個覆蓋
      }
      return React.cloneElement(elementTree, {
        className: 'hijacked',        // 修改屬性
      });
    }
  };
}

(f) 逐行拆解:

  • function withRenderHijack(WrappedComponent):慣例上 HOC 命名用 with 開頭,參數是「被包的組件」。
  • return class extends WrappedComponent:回傳一個匿名 class,extends 建立原型鏈(prototype chain),讓這個新 class 的實例可以往上找到 WrappedComponent.prototype 上的方法。
  • super.render()super 指向父類別的原型,所以這句是「呼叫父類別版本的 render,但 this 仍然是我自己」,因此原組件的 this.statethis.props 都讀得到。
  • React.cloneElement(elementTree, {...}):React 元素是不可變(immutable)的,不能直接改屬性,只能複製一份並覆寫。

三. 三個主要應用場景

(g) 條件式渲染:依 props 或 state 決定要不要渲染 super.render() 的內容。權限控制、Loading 狀態都屬於這一類。

(h) 修改/擴充元素樹:攔截回傳的 JSX,替換特定子元件、動態加 style / className、綁額外的事件監聽。

(i) 操縱 Props:在元素正式渲染前,攔截並替換子元件收到的 props。

四. 為什麼是「React 的 render 方法」,不是通用概念

(j) 「渲染」是所有框架的通用概念,但「渲染劫持」這個做法只在 React 成立,原因是 React 的 JSX 本質上就是 JavaScript 函式呼叫與物件,super.render() 回傳的是一個普通 JS 物件樹,你當然可以拿來讀跟改。Vue 的 template 與 Angular 的 template 在編譯後被框架接管,開發者拿不到那個中間結構。

(k) 三框架對照:

React Vue Angular
主要複用機制 HOC、Render Props、Custom Hooks(現代主流) Composition API(Composables)、Mixins、Scoped Slots 依賴注入(DI)、結構型指令(*ngIf)、裝飾器
能不能改元素樹 可以,JSX 就是 JS 物件 一般不行,template 由編譯器接管(除非自己寫 Render Function) 不直接改,改用 ViewContainerRef 操作視圖容器
心智模型 包一層函式 抽一支組合函式 注入一個服務或指令

(l) 面試提醒:如果被問到 HOC,記得補一句「React 16.8 之後官方推薦用 Custom Hooks 取代大部分 HOC 場景」。反向繼承因為會破壞組件的靜態方法繼承、讓 ref 難以傳遞、也不利於 React DevTools 除錯,實務上已經很少用。知道它是為了讀懂舊 codebase 與應付面試,不是為了拿來寫新專案。


程式碼範例

同層的 hoc-props-proxy-vs-inheritance-inversion.jsx 把兩種 HOC 流派並排寫出來對照。


練習題

題號 題名 為什麼相關
2666 Allow One Function Call 最小的「包一層、加行為」練習,HOC 的骨架
2623 Memoize 包一層並保留狀態,等同 HOC 快取 props 的心智模型
2632 Curry 函式回傳函式,理解高階函式的閉包基礎
2704 To Be Or Not To Be 回傳一個帶方法的物件,練習包裝者思維

上一篇
Day 8|瀏覽器拿到的只是一串純文字——從 打包到 HTML之Parsing、JS之V8如何編譯 以及script 標籤不同屬性值載入。三大框架共同的底層
系列文
現代函式庫與JavaScript的關係9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言