React 中的 HOC 和 Hooks

寫在前面

????????在函數式組件主導的 React 項目中,高階組件(HOC)并非首選推薦,更建議優先使用 Hooks來實現復用邏輯。核心原因是 HOC 存在固有的設計缺陷,而 Hooks 能更優雅、簡潔地解決相同問題,同時避免 HOC 的痛點。

????????本章節我們將分別介紹二者,并重點體會 Hooks 在函數式組件項目中的優勢。

目錄

HOC

一、是什么?

二、HOC 在函數組件項目中的 “不推薦原因”:痛點

1.?“包裝地獄”(Wrapper Hell):組件層級冗余

2.?邏輯復用 “不夠靈活”:強耦合于組件結構

Hooks

一、是什么?

二、判斷標準

三、核心作用

四、使用時注意事項

代碼對比:HOC?vs?Hooks

1. HOC

2. Hooks

總結


HOC

一、是什么?

HOC(Higher-Order Component,高階組件)是 React 早期(Class 組件時代)實現邏輯復用的核心方案,本質是 “一個接收組件、返回新組件的函數”。

它的核心價值是 “抽離通用邏輯(如權限、數據請求、狀態管理)”,讓多個組件復用這些邏輯。

例如:

// 一個封裝“登錄狀態判斷”的 HOC
const withAuth = (WrappedComponent) => {return (props) => {const isLogin = localStorage.getItem('token');if (!isLogin) return <Redirect to="/login" />;// 給被包裹組件注入 props 或增強邏輯return <WrappedComponent isLogin={isLogin} {...props} />;};
};// 使用:給需要登錄的組件注入登錄邏輯
const Profile = withAuth(({ isLogin }) => <div>歡迎回來</div>);

(參考文章:React 高階組件-CSDN博客)

二、HOC 在函數組件項目中的 “不推薦原因”:痛點

HOC 的設計是為 Class 組件服務的,在函數組件 + Hook 的生態中,其缺陷會被放大,導致代碼復雜度提升:

1.?“包裝地獄”(Wrapper Hell):組件層級冗余

每使用一個 HOC,就會給組件套一層 “容器組件”(如上面的?withAuth?會返回一個匿名函數組件)。如果多個 HOC 疊加(如?withAuth(withData(withTheme(Component)))),最終的組件樹會變得異常冗余:

withAuth → withData → withTheme → 目標組件

這種層級不僅增加 React DevTools 調試難度(需要層層穿透才能找到目標組件),還可能導致 props 透傳問題,導致狀態來源不清晰,props 混疊風險(若 HOC 未正確轉發?props,會丟失上層傳遞的屬性)。

2.?邏輯復用 “不夠靈活”:強耦合于組件結構

HOC 是 “組件級別的復用”—— 它只能將邏輯封裝到 “整個組件” 中,無法針對組件內的某部分邏輯(如一個按鈕的點擊處理、一段數據的格式化)進行復用。

例如:若兩個組件都需要 “格式化時間” 的邏輯,用 HOC 只能將 “時間格式化” 封裝成一個 HOC,再包裹整個組件;但用自定義 Hook(如?useFormatTime),可以直接在組件內調用,只復用這一段邏輯,無需包裝整個組件:

// 自定義 Hook:復用“時間格式化”邏輯(更靈活)
const useFormatTime = (time) => {return new Date(time).toLocaleString();
};// 組件內直接使用,無需包裝
const Card1 = ({ createTime }) => {const formatTime = useFormatTime(createTime);return <div>創建時間:{formatTime}</div>;
};const Card2 = ({ updateTime }) => {const formatTime = useFormatTime(updateTime);return <div>更新時間:{formatTime}</div>;
};

Hooks

一、是什么?

React 官方文檔對自定義 Hook 的定義是:

自定義 Hook 是一個函數,其名稱以 "use" 開頭,函數內部可以調用其他的 Hook(內置 Hook 或其他自定義 Hook)。

注意這里是?“可以調用”,不是 “必須調用”?內置 Hook。

二、判斷標準

判斷一個函數是不是自定義 Hook,關鍵看兩個點:

  1. 名稱是否以?use?開頭(強制規則);
  2. 是否用于復用 React 組件的邏輯(核心目的)。

至于是否包含?useState?等內置 Hook,只是 “自定義 Hook 能實現的功能范圍” 的區別 ——

  • 有內置 Hook,說明它能處理狀態 / 副作用;
  • 沒有內置 Hook,說明它處理的是純計算邏輯,但依然符合自定義 Hook 的定義,而且可以為未來擴展留空間:如果后續邏輯需要添加狀態(useState)、副作用(useEffect)或緩存(useMemo),無需重構調用方式,直接在函數內部添加即可,組件使用時完全無感知。

三、核心作用

Hook 是 React 16.8 引入的特性,本質是讓函數組件能夠使用狀態(State)和其他 React 特性(如生命周期、上下文等)的函數,核心價值體現在兩方面:

  1. 邏輯復用更簡潔
    解決了 Class 組件中 “邏輯復用需依賴高階組件(HOC)或 render props 導致的層級冗余” 問題。通過自定義 Hook,可將組件間的通用邏輯(如數據請求、表單處理、定時器管理等)抽離成獨立函數,直接在多個組件中復用,無需嵌套組件。

    例如:用?useFetch?封裝數據請求邏輯,在任何函數組件中直接調用即可復用,無需通過 HOC 包裝。

  2. 函數組件功能完善化
    讓函數組件從 “純展示” 升級為 “可擁有狀態和副作用” 的完整組件,無需再編寫 Class 組件。函數組件的代碼更簡潔、可讀性更強,避免了 Class 組件中?this?指向混亂、生命周期函數邏輯混雜等問題。

四、使用時注意事項

React 對 Hook 的使用有嚴格規則,違反規則可能導致組件狀態異常或邏輯錯誤,需特別注意:

1. 只能在函數組件或自定義 Hook 中調用

原因:Hook 依賴 React 內部的 “調用棧” 追蹤狀態歸屬,只有在函數組件 / 自定義 Hook 中調用,才能確保狀態與組件正確關聯。

  • 禁止在 Class 組件中使用 Hook;
  • 禁止在普通 JavaScript 函數(非 Hook)中調用 Hook(如事件處理函數、定時器回調等)。


2.?只能在函數的頂層調用

禁止在條件判斷(if)、循環(for)、嵌套函數(如?map?回調)中調用 Hook。

示例(錯誤):?

const MyComponent = () => {if (someCondition) {const [count, setCount] = useState(0); // ? 不能在條件中調用}// ...
};

原因:React 依賴 Hook 的調用順序來識別和關聯狀態。如果在條件 / 循環中調用,每次渲染時 Hook 的調用順序可能變化,導致 React 無法正確匹配狀態與 Hook。

3. 自定義 Hook 必須以?use?開頭命名
例如?useFetchuseTimer,而非?fetchDatatimer

原因:這是 React 的強制約定,便于開發者識別 Hook,同時讓 ESLint 插件(如?eslint-plugin-react-hooks)能自動檢查 Hook 使用規則,避免錯誤。

4. 依賴數組的準確性(針對?useEffectuseMemo?等)
對于帶依賴數組的 Hook(如?useEffect(fn, deps)),需確保依賴數組包含所有在 Hook 內部使用的 “外部變量”( props、狀態、組件內定義的函數等)。

示例(錯誤):?

const MyComponent = ({ id }) => {const [data, setData] = useState(null);useEffect(() => {fetch(`/api/${id}`).then(res => setData(res)); }, []); // ? 遺漏依賴 id,id 變化時不會重新請求
};

原因:依賴數組決定了 Hook 何時重新執行。遺漏依賴會導致 Hook 捕獲舊值,引發邏輯錯誤;多余依賴則會導致不必要的重復執行,浪費性能。

5. 避免在 Hook 內部定義組件
禁止在 Hook 中定義函數組件,否則每次 Hook 調用都會創建新的組件類型,導致 React 卸載舊組件、重新掛載新組件(而非更新),丟失組件狀態。

示例(錯誤):?

const useCustomHook = () => {const InnerComponent = () => <div>Hello</div>; // ? 不應在 Hook 中定義組件return InnerComponent;
};

代碼對比:HOC?vs?Hooks

1. HOC

當使用?withAuth(withData(withTheme(UserProfile)))?時,最終的代碼會是這樣的:

import React from 'react';// 1. 第一個HOC:處理主題
const withTheme = (Component) => {return (props) => {const theme = { color: 'blue', background: 'white' };return <Component {...props} theme={theme} />;};
};// 2. 第二個HOC:處理數據加載
const withData = (Component) => {return (props) => {const data = { user: 'John', age: 30 }; // 模擬API數據return <Component {...props} data={data} />;};
};// 3. 第三個HOC:處理權限驗證
const withAuth = (Component) => {return (props) => {const isAuthenticated = true; // 模擬登錄狀態if (!isAuthenticated) {return <div>請先登錄</div>;}return <Component {...props} isAuthenticated={isAuthenticated} />;};
};// 原始業務組件
const UserProfile = (props) => {return (<div style={{ color: props.theme.color }}>{props.isAuthenticated && (<div><h1>用戶信息</h1><p>姓名:{props.data.user}</p><p>年齡:{props.data.age}</p></div>)}</div>);
};// 多個HOC疊加使用
const EnhancedUserProfile = withAuth(withData(withTheme(UserProfile)));// 最終渲染組件
function App() {return (<div><EnhancedUserProfile /></div>);
}

2. Hooks

import React, { useState } from 'react';// 1. 自定義Hook:處理主題
const useTheme = () => {const theme = { color: 'blue', background: 'white' };return theme;
};// 2. 自定義Hook:處理數據加載
const useData = () => {const data = { user: 'John', age: 30 }; // 模擬API數據return data;
};// 3. 自定義Hook:處理權限驗證
const useAuth = () => {const [isAuthenticated] = useState(true); // 模擬登錄狀態return isAuthenticated;
};// 業務組件(直接使用Hook)
const UserProfile = () => {// 直接在組件中調用Hook獲取所需功能const theme = useTheme();const data = useData();const isAuthenticated = useAuth();if (!isAuthenticated) {return <div>請先登錄</div>;}return (<div style={{ color: theme.color }}><div><h1>用戶信息</h1><p>姓名:{data.user}</p><p>年齡:{data.age}</p></div></div>);
};// 最終渲染組件
function App() {return (<div><UserProfile /></div>);
}

總結

維度HOC(高階組件)Hooks(鉤子函數)
優點1. 兼容 Class 組件和函數組件;
2. 邏輯封裝邊界清晰(基于組件隔離)
1. 代碼更簡潔,無組件嵌套冗余;
2. 邏輯與組件結合更緊密,無需通過 props 傳遞數據;
3. 支持細粒度邏輯拆分(一個組件可調用多個 Hook);
4. 學習成本更低(無需理解 “組件嵌套”“閉包陷阱” 等復雜概念)
缺點1. 易產生 “組件層級嵌套地獄”(多個 HOC 疊加導致 DevTools 中組件樹混亂);
2. 邏輯傳遞依賴 props,易出現 “props 透傳”(多層組件需手動傳遞 props);
3. 可能引發 “閉包陷阱”(HOC 捕獲舊的 props/state);
4. 無法在組件內部動態切換 HOC 邏輯
1. 僅支持函數組件,不兼容 Class 組件;
2. 需嚴格遵循使用規則(如只能在頂層調用、依賴數組需準確);
3. 復雜邏輯的 Hook 可能存在 “依賴管理復雜” 問題(需精準維護?useEffect?依賴)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/96004.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/96004.shtml
英文地址,請注明出處:http://en.pswp.cn/web/96004.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【 蒼穹外賣 | Day2】

1. 相關視頻 Day2的全部視頻集數 2. 學習記錄 2.1 對象屬性拷貝 當DTO與實體類或者VO對象之間的一個裝換的時候&#xff0c;如果通過new創建對象&#xff0c;然后調用set方法進行屬性賦值&#xff0c;不夠方便&#xff0c;代碼不夠簡潔。當屬性過多時候&#xff0c;代碼就會…

焊接自動化測試平臺圖像處理分析-模型訓練推理

1、使用技術棧&#xff1a;jdk17/springboot/python/opencv/yolov8 2、JAVA環境搭建 JDK17下載安裝&#xff1a;wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz 解壓軟件 tar -xf jdk-17.0.16_linux-x64_bin.tar.gz 配置全局變量 vim /etc/p…

【python實用小腳本-205】[HR揭秘]手工黨逐行查Bug的終結者|Python版代碼質量“CT機”加速器(建議收藏)

1. 場景故事 “作為HR&#xff0c;我曾用2小時逐行審閱50份Python簡歷項目&#xff0c;直到發現候選人的代碼復雜度超標導致線上事故…” → 轉折點&#xff1a;用麥凱布&#xff08;McCabe&#xff09;圈復雜度檢測腳本&#xff0c;30秒掃描全倉庫&#xff0c;現可100%攔截“高…

LeetCode - 1089. 復寫零

題目 1089. 復寫零 - 力扣&#xff08;LeetCode&#xff09; 思路 這道題我首先想到的是從前往后雙指針&#xff0c;但是這樣做會造成數據的覆蓋&#xff0c;比如說下面的這個情況 所以解決的方法就是從后往前去復寫&#xff0c;但是從后往前的話就要知道最后一個有效元素是…

c#中public類比博圖

簡單來說&#xff0c;**public 定義了“接口”或“引腳”**&#xff0c;就像你的FB塊上的 Input, Output, InOut 管腳一樣。它決定了外部的其他代碼&#xff08;如另一個FB或OB1&#xff09;可以看到和操作這個塊里的什么東西。讓我用你最熟悉的博圖概念來詳細類比一下。---###…

K8s基于節點軟親和的高 CPU Pod 擴容與優先調度方案

場景與目標 集群節點&#xff1a;master&#xff08;4 核&#xff09;、node1&#xff08;16 核&#xff09;、node2&#xff08;16 核&#xff09;。目標&#xff1a;將一個高 CPU 消耗的工作負載橫向擴展到 4 個實例&#xff0c;并通過**節點親和性&#xff08;軟親和&#…

MySQL InnoDB 的鎖機制

引言 鎖是數據庫管理并發訪問的另一種核心機制&#xff0c;與 MVCC 相輔相成。本文將系統梳理 MySQL InnoDB 中鎖的粒度、類型和工作原理&#xff0c;并深入探討它如何與事務隔離級別配合&#xff0c;共同保障數據的一致性和完整性。 一、 鎖的粒度&#xff1a;由粗到細 InnoD…

狀態模式(State Pattern)——網絡連接場景的 C++ 實戰

一、為什么要用狀態模式&#xff1f;在開發中&#xff0c;經常遇到“對象在不同狀態下行為不同”的情況。最常見的寫法是用一堆 if/else 或 switch 來判斷狀態&#xff0c;然后在不同分支里寫邏輯。這樣做有兩個問題&#xff1a;狀態增多后&#xff0c;條件分支會變得臃腫。修改…

使用csi-driver-nfs實現K8S動態供給

文章目錄一、部署NFS二、k8s環境部署csi-nfs三、測試動態供給補充應用服務器IPnfs-server192.168.1.5k8s-master01192.168.1.1k8s-node01192.168.1.2k8s-node02192.168.1.3 一、部署NFS 1、在NFS服務端和k8s所有節點部署nfs-utils 因為客戶端去掛載nfs服務端的共享目錄時&…

【開題答辯全過程】以 基于ssm的房屋中介管理系統為例,包含答辯的問題和答案

個人簡介一名14年經驗的資深畢設內行人&#xff0c;語言擅長Java、php、微信小程序、Python、Golang、安卓Android等開發項目包括大數據、深度學習、網站、小程序、安卓、算法。平常會做一些項目定制化開發、代碼講解、答辯教學、文檔編寫、也懂一些降重方面的技巧。感謝大家的…

MySQL主從復制之進階延時同步、GTID復制、半同步復制完整實驗流程

1.主從同步1.1主從同步原理是指將主庫的DDL和DML操作通過二進制日志(binlog)傳到從庫服務器&#xff0c;然后在從庫上對這些日志進行重新執行&#xff0c;從而使從庫和主庫數據保持一致1.2環境設置庫名ip地址操作系統mysql版本主庫msyql-master192.168.31.228rhel7.9源碼安裝my…

織信低代碼:用更聰明的方式,把想法變成現實!

你有沒有過這樣的時刻&#xff1f;想親手做一個應用&#xff0c;卻因為“不會編碼”而遲遲沒有開始&#xff1b;或曾無奈地目睹公司里一個看似簡單的需求&#xff0c;硬是耗費數月、投入大量人力反復開發……現在&#xff0c;有一類工具正在改變這一切。它叫低代碼。而今天我們…

【序列晉升】28 云原生時代的消息驅動架構 Spring Cloud Stream的未來可能性

目錄 一、Spring Cloud Stream是什么&#xff1f; 二、誕生背景與設計動機 2.1 微服務架構的挑戰 2.2 Spring生態的發展 2.3 Spring Integration的演進 三、架構設計與核心組件 3.1 分層架構設計 3.2 核心組件詳解 3.3 編程模型 四、解決的問題與優勢 4.1 解決的核心…

內網后滲透攻擊--linux系統(權限維持)

用途限制聲明&#xff0c;本文僅用于網絡安全技術研究、教育與知識分享。文中涉及的滲透測試方法與工具&#xff0c;嚴禁用于未經授權的網絡攻擊、數據竊取或任何違法活動。任何因不當使用本文內容導致的法律后果&#xff0c;作者及發布平臺不承擔任何責任。滲透測試涉及復雜技…

C++筆記之同步信號量、互斥信號量與PV操作再探(含軟考題目)

C++筆記之同步信號量、互斥信號量與PV操作再探(含軟考題目) code review! 參考筆記: 1.C++筆記之同步信號量、互斥信號量與PV操作再探(含軟考題目) 2.C++筆記之信號量、互斥量與PV操作 參考鏈接 1.嵌入式基礎知識-信號量,PV原語與前趨圖 2.信號量、PV操作及軟考高級試題解析…

布隆過濾器:快速判斷某個元素是否存在

特點&#xff1a;高效、空間占用小、允許一定誤判 布隆過濾器在 Redis 里的實現機制&#xff0c;核心就是&#xff1a;用一個大位圖&#xff08;bitmap&#xff09;來表示集合 位圖長度 m 初始值都是 0 插入元素時通過 k 個不同的哈希函數&#xff0c;對元素做哈希 每個哈希結…

C# 修改基類List中某一元素的子類類型

描述&#xff1a;基類&#xff1a;BaseClass子類1&#xff1a;A子類2&#xff1a;B然后我有一個List<BaseClass>類型的鏈表:list&#xff0c;我先往list中添加了兩個元素&#xff1a;第一個元素為A類型&#xff0c;第二個元素為B類型&#xff0c;然后我想改變第一個元素類…

基于STM32智能陽臺監控系統

基于STM32智能陽臺監控系統&#xff08;程序&#xff0b;原理圖元件清單&#xff09;功能介紹具體功能&#xff1a;1.采用STM32作為主控芯片實現檢測和控制&#xff1b;2.通過光敏電阻采集光線&#xff0c;將當前光線值在LCD1602顯示&#xff0c;低于50%控制LED亮&#xff0c;高…

動態維護有效區間:滑動窗口

右指針不斷移動獲取解&#xff0c;左指針不斷移動縮小解范圍 左指針的意義非常重要&#xff0c;相當于一個標兵&#xff0c;不斷與這個標兵進行比較&#xff0c;如果符合要求&#xff0c;這左指針進行移動&#xff0c;并進行操作&#xff0c;如果不符合要求&#xff0c;則左指針…

嵌入式學習---(單片機)

1.UART的概念通用異步收發器&#xff0c;2個串口&#xff08;1個串口被用于ISP下載程序&#xff0c;1個串口被用于和主機之間的通信&#xff09;&#xff0c;RXD(接收信號線) TXD(發送信號線)2、單工、半雙工、全雙工概念對比維度單工&#xff08;Simplex&#xff09;半雙工&am…