【React Hooks】封裝的藝術:如何編寫高質量的 React 自-定義 Hooks

【React Hooks】封裝的藝術:如何編寫高質量的 React 自-定義 Hooks

所屬專欄: 《前端小技巧集合:讓你的代碼更優雅高效》
上一篇: 【React State】告別 useState 濫用:何時應該選擇 useReducer
作者: 碼力無邊


? 引言:你的組件里,是否藏著一個“代碼克隆人”?

嘿,各位在 React 世界里追求代碼之美的道友們,我是碼力無邊

隨著我們對 useStateuseEffectuseReducer 等基礎 Hooks 的運用日漸純熟,我們的組件功能也變得越來越強大。但與此同時,一個新的“心魔”開始悄然滋生——重復的邏輯

請審視一下你寫的組件,是否也曾遇到過這樣的場景:

  • 組件 A:需要從 localStorage 讀取一個值,并在用戶修改時寫回。
  • 組件 B:也需要從 localStorage 讀取另一個值,并在用戶修改時寫回。
  • 于是你把那段包含 useStateuseEffect 的邏輯,在 A 和 B 中復制粘貼了一遍。

又或者:

  • 組件 C:需要監聽窗口的寬度變化,以實現響應式布局。
  • 組件 D:也需要監聽窗口的寬度,來決定顯示不同的內容。
  • 于是你又把那段包含 useStateuseEffect 來綁定 resize 事件的邏輯,在 C 和 D 中又復制粘貼了一遍。

這種“代碼克隆”的行為,就像在你的項目中制造了一堆長得一模一樣的“克隆人”。他們分散在各個角落,一旦你需要修改他們的行為邏輯(比如,給 localStorage 加上異常處理),你就必須找到所有的“克隆人”,逐一進行修改,極其繁瑣且容易遺漏,是 bug 的溫床。

在 Class Component 時代,我們用高階組件 (HOC)渲染屬性 (Render Props) 這些模式來解決邏輯復用問題。它們很強大,但也帶來了“包裝地獄 (Wrapper Hell)”和代碼可讀性下降等問題。

而 Hooks 的出現,為我們帶來了一種更優雅、更直觀、更強大的邏輯復用范式——自定義 Hooks (Custom Hooks)

自定義 Hook 不是什么新奇的魔法,它就是一個普通的 JavaScript 函數,其名稱以 use 開頭,函數內部可以調用其他的 Hooks (如 useState, useEffect 等)。它的出現,讓我們能夠將組件的狀態邏輯從 UI 中抽離出來,變成一個獨立的、可復用的單元。

今天,碼力無邊就將帶你進入 Hooks 的封裝藝術殿-堂,手把手教你如何編寫高質量的自定義 Hooks,將你項目中的那些“代碼克隆人”徹底消滅,讓你的代碼庫變得干凈、優雅、且充滿“智慧”。

一、自定義 Hook 的“開光儀式”:命名與規則

在開始創造之前,我們必須先了解自定義 Hook 的兩條“天規”:

  1. 名稱必須以 use 開頭:比如 useLocalStorage, useWindowSize。這不是一個隨意的約定,而是 React Linter 用來檢查 Hooks 規則(比如,不能在條件語句中調用 Hooks)的重要依據。不遵守這個規則,React 就無法判斷你的函數是否是一個 Hook。
  2. 只能在 React 函數組件或其他的自定義 Hook 中調用:你不能在普通的 JavaScript 函數(非組件或非 Hook)中調用它。

好了,“開光儀式”結束,讓我們開始創造第一個屬于自己的 Hook!

二、實戰一:打造你的“本地存儲神器”——useLocalStorage

這是最經典、最實用的自定義 Hook 之一。

需求: 創建一個 Hook,它的用法和 useState 幾乎一樣,但它能自動將狀態持久化到 localStorage 中。

第一步:識別重復邏輯
在沒有自定義 Hook 之前,我們的組件可能是這樣寫的:

function UserProfile() {const [name, setName] = useState(() => {// 從 localStorage 初始化 stateconst savedName = window.localStorage.getItem('username');return savedName || 'Guest';});// 當 name 變化時,同步到 localStorageuseEffect(() => {window.localStorage.setItem('username', name);}, [name]);// ... render logic
}

這段“從 localStorage 初始化,并用 useEffect 同步回去”的邏輯,就是我們要抽離的“重復基因”。

第二步:創建自定義 Hook
我們來創建一個 useLocalStorage.js 文件:

import { useState, useEffect } from 'react';function useLocalStorage(key, initialValue) {// 1. 創建一個 state,其初始化邏輯和之前組件里的一樣const [storedValue, setStoredValue] = useState(() => {try {const item = window.localStorage.getItem(key);// 如果 localStorage 中有值,就用它;否則,用初始值return item ? JSON.parse(item) : initialValue;} catch (error) {// 如果解析出錯,也返回初始值console.error(error);return initialValue;}});// 2. 使用 useEffect 來監聽 storedValue 的變化useEffect(() => {try {// 當 storedValue 變化時,將其序列化并存入 localStoragewindow.localStorage.setItem(key, JSON.stringify(storedValue));} catch (error) {console.error(error);}}, [key, storedValue]); // 依賴項是 key 和 value// 3. 返回一個和 useState 簽名一樣的數組return [storedValue, setStoredValue];
}export default useLocalStorage;

第三步:在組件中使用
現在,我們的組件可以變得極其簡潔:

import useLocalStorage from './useLocalStorage';function UserProfile() {// 一行代碼,搞定狀態和持久化!const [name, setName] = useLocalStorage('username', 'Guest');return (<div><input type="text" value={name} onChange={e => setName(e.target.value)} /><p>Hello, {name}!</p></div>);
}function ThemeSwitcher() {// 在另一個組件中復用!const [theme, setTheme] = useLocalStorage('theme', 'light');return (<div className={theme}><button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Switch to {theme === 'light' ? 'dark' : 'light'} mode</button></div>);
}

看到了嗎? 我們成功地將狀態管理的復雜邏輯(初始化、try...catchuseEffect 同步)封裝進了 useLocalStorage 這個黑盒子里。組件的使用者,只需要像使用 useState 一樣,簡單地調用它,就能獲得“狀態 + 持久化”的超能力。這就是自定義 Hook 的魔力!

三、實戰二:你的“響應式布局之眼”——useWindowSize

需求: 創建一個 Hook,實時返回當前瀏覽器窗口的寬度和高度。

第一步:識別重復邏輯
獲取窗口尺寸的邏輯通常是:

  1. useState 存儲 widthheight
  2. useEffect 在組件掛載時綁定 window.resize 事件監聽。
  3. 在事件處理函數中,用 setState 更新尺寸。
  4. 非常重要:在 useEffect清理函數中,移除事件監聽,防止內存泄漏。

第二步:創建自定義 Hook
我們來創建一個 useWindowSize.js 文件:

import { useState, useEffect } from 'react';function useWindowSize() {const [windowSize, setWindowSize] = useState({width: undefined,height: undefined,});useEffect(() => {// 1. 定義事件處理函數function handleResize() {setWindowSize({width: window.innerWidth,height: window.innerHeight,});}// 2. 添加事件監聽window.addEventListener('resize', handleResize);// 3. 首次調用,以獲取初始尺寸handleResize();// 4. 返回一個清理函數,在組件卸載時移除監聽return () => window.removeEventListener('resize', handleResize);}, []); // 空依賴數組,確保 effect 只在掛載和卸載時運行return windowSize;
}export default useWindowSize;

第三步:在組件中使用

import useWindowSize from './useWindowSize';function ResponsiveComponent() {// 一行代碼,獲得響應式的窗口尺寸!const { width, height } = useWindowSize();if (width < 768) {return <div>我是移動端布局</div>;}return (<div><h1>我是桌面端布局</h1><p>當前窗口尺寸: {width} x {height}</p></div>);
}

這個 Hook 將所有關于事件監聽、狀態更新和內存清理的底層細節都封裝了起來,讓組件可以專注于如何使用這些數據,而不是如何獲取它們。這完美體現了“關注點分離”的原則。

四、編寫高質量自定義 Hook 的“心法”

一個好的自定義 Hook,應該像 React 內置的 Hook 一樣,具備良好的設計和DX (開發者體驗)。

  1. 明確的輸入和輸出

    • 輸入 (參數): 參數應該清晰明了,就像 useLocalStoragekeyinitialValue
    • 輸出 (返回值): 返回值的設計很重要。
      • 如果你的 Hook 像 useState 一樣,返回一個狀態值和一個更新函數,那么返回一個數組 [value, setValue] 是一個很好的約定,因為它允許調用者自由命名。
      • 如果你的 Hook 返回多個獨立的值(比如 useWindowSizewidthheight),那么返回一個對象 { width, height } 更具可讀性,并且未來更容易擴展(增加新返回值而不會破壞現有用法)。
  2. 保持純粹和可預測

    • Hook 內部的邏輯應該主要圍繞著 React 的狀態和生命周期。避免在 Hook 內部執行一些不可預測的、與組件狀態無關的副作用。
    • 遵循 Hooks 的規則,不要在循環或條件中調用其他 Hooks。
  3. 通用性和可配置性

    • 設計 Hook 時,思考它是否能被用在更多場景。比如,我們的 useLocalStorage 就可以處理任何可序列化的數據,而不僅限于字符串。
    • 適時地提供配置選項作為參數,讓 Hook 更靈活。
  4. 自給自足,不暴露實現細節

    • 一個好的 Hook 應該是一個“黑盒子”。它管理自己的所有內部狀態和副作用(比如事件監聽的清理)。調用者無需關心其內部實現。

寫在最后:自定義 Hook 是你的“超能力工廠”

自定義 Hooks 是 React 賦予我們開發者的一項“超能力”。它讓我們能夠超越組件的界限,去創造、組合和分享我們自己的“狀態邏輯積木”。

當你下一次在不同的組件間復制粘貼一段 useState + useEffect 的代碼時,請停下來。這正是你創造一個新 Hook 的信號!

將重復的邏輯封裝成一個自定義 Hook,就像是建立了一座“超能力工廠”。從此以后,任何組件想要獲得這項“超能力”,只需要去工廠里“領取”(import) 一下即可。你的代碼庫將因此變得更加模塊化、可維護性更高,你的開發效率也會得到質的飛躍。

這,就是封裝的藝術,也是 React Hooks 設計哲學的精髓所在。


專欄預告與互動:

我們已經學會了封裝可復用的邏輯。但在大型應用中,我們還需要在組件樹的“遠房親戚”之間共享狀態。一層層地 props drilling (屬性鉆孔) 顯然不是個好主意。

下一篇,我們將深入探討 React 的官方“跨層傳功”解決方案——Context API。你將學習如何使用它來避免 props drilling,并探討一個經典問題:Context API 是性能殺手嗎?我們又該如何正確地優化它?

感覺碼力無邊的“封裝藝術”讓你對 Hooks 有了全新的認識?別忘了點贊、收藏、關注,你的每一次支持,都是我建造下一座“超能力工廠”的圖紙和動力!

今日挑戰: 我們可以結合之前學過的知識,創造一個 useDebounce Hook 嗎?這個 Hook 接收一個值和一個延遲時間,返回一個經過防抖處理后的值。把你的實現思路或代碼片段分享在評論區,讓我們一起打造這個非常實用的 Hook!

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

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

相關文章

華為GaussDB的前世今生:國產數據庫崛起之路

在數據庫領域&#xff0c;華為GaussDB已成為一顆耀眼的明星&#xff0c;為企業核心業務數字化轉型提供堅實的數據底座。但這并非一蹴而就&#xff0c;其背后是長達二十余年的技術沉淀、戰略投入與持續創新。本文將深入探尋華為GaussDB的歷史沿革與核心技術細節&#xff0c;展現…

數據結構初階(16)排序算法——歸并排序

2.4 歸并排序 歸并排序&#xff08;Merge Sort&#xff09;是基于分治思想的經典排序算法。核心邏輯&#xff1a; 分而治之——把復雜排序問題拆分成簡單子問題解決&#xff0c;再合并子問題的結果。聯系鏈表的合并&#xff1a;兩個有序鏈表l1、l2創建新鏈表l3&#xff08;帶頭…

MATLAB實現匈牙利算法求解二分圖最大匹配

MATLAB實現匈牙利算法求解二分圖最大匹配 匈牙利算法&#xff08;也稱為Kuhn-Munkres算法&#xff09;是解決二分圖最大匹配問題的經典算法。 代碼 function [matching, max_match] hungarian_algorithm(adjMatrix)% HUNGARIAN_ALGORITHM 實現匈牙利算法求解二分圖最大匹配% 輸…

自定義table

更好<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"utf-8"><title>數據表格</title><style>* {margin: 0;padding: 0;box-sizing: border-box;font-size: 14px;}html,body {width: 100%;height: 100%…

面向R語言用戶的Highcharts

如果您喜歡使用 R 進行數據科學創建交互式數據可視化&#xff0c;那么請你收藏。今天&#xff0c;我們將使用折線圖、柱狀圖和散點圖來可視化資產回報。對于我們的數據&#xff0c;我們將使用以下 5 只 ETF 的 5 年月回報率。 SPY (S&P500 fund)EFA (a non-US equities fun…

【測試工具】OnDo SIP Server--輕松搭建一個語音通話服務器

前言 Ondo SIP Server 是一款基于 SIP(Session Initiation Protocol)協議的服務器軟件&#xff0c;主要用于實現 VoIP(Voice over IP)通信&#xff0c;支持語音通話、視頻會議等多媒體會話管理&#xff0c;非常適合學習和測試VoIP的基本功能。本文介紹Ondo SIP Server的安裝、…

瘋狂星期四文案網第42天運營日記

網站運營第42天&#xff0c;點擊觀站&#xff1a; 瘋狂星期四 crazy-thursday.com 全網最全的瘋狂星期四文案網站 運營報告 今日訪問量 今日搜索引擎收錄情況 網站優化點 優化一些發現的seo錯誤 增加顏文字欄目 增加了一些tag

使用空模型實例調用輔助函數,確定在量化過程中哪些層會被跳過(43)

在Facebook的OPT-350M中,模型的頭部(lm_head)與解碼器的嵌入標記層(decoder.embed_tokens)共享其權重。 print(model.model.decoder.embed_tokens) print(model.lm_head)輸出結果 Embedding(50272, 512

從0-1使用Fastmcp開發一個MCP服務,并部署到阿里云百煉 -持續更新中

目的&#xff1a; 在本地使用fastmcp開發一個mcp,然后注冊到阿里云的百煉里面。實現在百煉里面創建智能體的時候直接引用自己開發的MCP 已完成&#xff1a;本地環境安裝 待完成&#xff1a; 1.根據需求實現一個MCP中可以調用某應用的多個API即 mcp.tool()、mcp.prompt()、接入大…

設計模式之匯總

設計模式 零、設計原則 0.1 單一職責 0.2 接口隔離 0.3 開閉原則 0.4 依賴倒置0.5 迪米特法則&#xff0c;最小知道原則用戶關機 只和朋友通信 朋友條件&#xff1a; 1&#xff09;當前對象本身&#xff08;this&#xff09; 2&#xff09;以參量形式傳入到當前對象方法中的對象…

第6章 Decoder與Encoder核心組件

前言 Netty從底層Java通道讀取ByteBuf二進制數據&#xff0c;傳入Netty通道的流水線&#xff0c;隨后開始入站處理。在入站處理過程中&#xff0c;需要將ByteBuf二進制類型解碼成Java POJO對象。這個解碼過程可以通過Netty的Decoder&#xff08;解碼器&#xff09;去完成。 在…

[已解決]當啟動 Spring Boot 應用時出現 Using generated security password xxx提示

當啟動 Spring Boot 應用時出現 Using generated security password xxx提示當啟動 Spring Boot 應用時出現 Using generated security password xxx提示&#xff0c;這是 Spring Security 自動配置的默認行為&#xff0c;通常發生在你??未自定義安全配置??但引入了 Spring…

自動分析需求,PRD 生成只需 SOLO 一步!

資料來源&#xff1a;火山引擎-開發者社區 寫不清需求&#xff1f;PRD 難產&#xff1f;開發總跑偏&#xff1f;這些痛點&#xff0c;SOLO 來解決。 TRAE SOLO 是行業首個 Context Engineer。它不止協助編碼&#xff0c;更能基于精準上下文理解和工具調用&#xff0c;從構思、…

物聯網軟件開發過程中,數據流圖(DFD),用例圖,類圖,活動圖,序列圖,狀態圖,實體關系圖(ERD),BPMN(業務流程建模)詳解分析

概述軟件開發過程中&#xff0c;特別是在物聯網&#xff08;IoT&#xff09;場景中&#xff0c;數據流圖&#xff08;DFD&#xff09;、UML圖&#xff08;包括用例圖、類圖、活動圖、序列圖、狀態圖&#xff09;、實體關系圖&#xff08;ERD&#xff09;和業務流程建模&#xf…

Mac(一)常用的快捷鍵整理

目錄1、系統操作與窗口管理2、應用與窗口切換3、常規編輯操作4、文本導航與光標控制??5、文本格式與文檔功能&#xff08;支持應用中&#xff09;6、截圖快捷鍵7、Safari 瀏覽器快捷鍵8、Finder 快捷鍵&#xff08;文件管理&#xff09;9、Fn / Globe 功能鍵&#xff08;部分…

HAProxy使用方法以及和LVS區別

HAProxy簡介HAProxy是法國開發者 威利塔羅(Willy Tarreau) 在2000年使用C語言開發的一個開源軟件 是一款具備高并發(萬級以上)、高性能的TCP和HTTP負載均衡器 支持基于cookie的持久性&#xff0c;自動故障切換&#xff0c;支持正則表達式及web狀態統計LVS 與 HAProxy 的核心區別…

超越“小作文”:大模型指令設計的進階之路——優化知識信噪比

文章摘要&#xff1a;你是否認為&#xff0c;給大模型的指令&#xff08;Prompt&#xff09;寫得越詳細越好&#xff1f;真的是信息越多&#xff0c;模型就越懂你嗎&#xff1f;本文將深入探討一個反直覺的觀點&#xff1a;初級的指令設計專注於資訊的堆砌&#xff0c;而高階的…

elasticsearch-集成prometheus監控(k8s)

一. 簡介&#xff1a; 關于elasticsearch的簡介和部署&#xff0c;可以參考單獨的文章elasticsearch基礎概念與集群部署-CSDN博客&#xff0c;這里就不細說了。這里只講講如何在k8s中部署export并基于prometheus做es的指標采集。 二. 實現方式&#xff1a; 首先我們需要先部署…

貪心算法(Greedy Algorithm)詳解

一、什么是貪心算法&#xff1f; 貪心算法是一種算法設計范式&#xff0c;指在解決問題時&#xff0c;依賴于每次選擇最優的局部解&#xff0c;以期最終得到全局最優解。貪心算法的關鍵特點是&#xff1a; 局部最優選擇&#xff1a;每個階段選擇當前看起來最好的選擇&#xff0…

電梯的構造|保養|維修視頻全集_電梯安全與故障救援(課程下載)

課程下載&#xff1a;https://download.csdn.net/download/m0_66047725/91699586 電梯原理與維修視頻教程 相關簡介: 電梯現在運用的非常廣泛,比如大型商場,建筑工地,特別是現在建造的很多高樓、商品房,基本都是安裝了電梯。電梯維保不力是導致電梯運行中安全事故頻發的主要原…