首頁 >web前端 >js教程 >如何建立自己的 React Hook:逐步指南

如何建立自己的 React Hook:逐步指南

王林
王林原創
2024-08-09 14:32:36672瀏覽

How to Build Your Own React Hooks: A Step-by-Step Guide

自訂 React 掛鉤是一個重要的工具,可讓您為 React 應用程式添加特殊、獨特的功能。

在許多情況下,如果您想為應用程式添加某個功能,您只需安裝專為解決您的問題而設計的第三方程式庫即可。但如果這樣的函式庫或鉤子不存在,你該怎麼辦?

身為 React 開發人員,學習建立自訂掛鉤來解決問題或在自己的 React 專案中加入缺失功能的過程非常重要。

在這個逐步指南中,我將向您展示如何透過分解我為自己的應用程式製作的三個鉤子來創建您自己的自訂 React 鉤子,以及創建它們是為了解決哪些問題。

  1. useCopyToClipboard 鉤子 在我的網站 reedbarger.com 的過去版本中,我允許用戶使用名為「react-copy-to-clipboard」的套件從我的文章中複製程式碼。

使用者只需將滑鼠懸停在程式碼片段上,點擊剪貼簿按鈕,程式碼就會添加到電腦的剪貼簿中,以便他們可以在任何地方貼上和使用程式碼。

複製-gif.gif
然而,我不想使用第三方函式庫,而是想使用我自己的自訂 React hook 重新建立此功能。與我創建的每個自訂 React hook 一樣,我將其放在專用資料夾中,通常稱為 utils 或 lib,專門用於我可以在應用程式中重複使用的函數。

我們將把這個鉤子放在一個名為 useCopyToClipboard.js 的檔案中,然後我將建立一個同名的函數。

我們可以透過多種方式將一些文字複製到使用者的剪貼簿。我更喜歡為此使用一個庫,這使得該過程更加可靠,稱為複製到剪貼簿。

它導出一個函數,我們稱之為copy。

// utils/useCopyToClipboard.js
從“react”導入React;
從「複製到剪貼簿」匯入副本;

匯出預設函數useCopyToClipboard() {}
接下來,我們將建立一個函數,用於複製要新增到使用者剪貼簿的任何文字。我們將呼叫這個函數handleCopy。

如何製作handleCopy函數
在函數中,我們首先需要確保它只接受字串或數字類型的資料。我們將設定一個 if-else 語句,這將確保類型是字串或數字。否則,我們將在控制台記錄錯誤,告訴使用者您無法複製任何其他類型。

從「react」匯入React;
從「複製到剪貼簿」匯入副本;

匯出預設函數useCopyToClipboard() {
const [isCopied, setCopied] = React.useState(false);

函數handleCopy(text) {
if (typeof text === "string" || typeof text == "number") {
// 複製
} 其他 {
// 不要複製
控制台.錯誤(
無法將 typeof ${typeof text} 複製到剪貼簿,必須是字串或數字。
);
}
}
}
接下來,我們獲取文字並將其轉換為字串,然後將其傳遞給複製函數。從那裡,我們將 handleCopy 函數從鉤子返回到應用程式中我們喜歡的任何位置。

一般來說,handleCopy函數會連接到按鈕的onClick。

從「react」匯入React;
從「複製到剪貼簿」匯入副本;

匯出預設函數useCopyToClipboard() {
函數handleCopy(text) {
if (typeof text === "string" || typeof text == "number") {
複製(text.toString());
} 其他 {
控制台.錯誤(
無法將 typeof ${typeof text} 複製到剪貼簿,必須是字串或數字。
);
}
}

返回句柄複製;
}
此外,我們需要一些狀態來表示文字是否被複製。為了創建它,我們將在鉤子頂部呼叫 useState 並建立一個新的狀態變數 isCopied,其中 setter 將被稱為 setCopy。

最初該值將為 false。如果文字複製成功,我們將把 copy 設為 true。否則,我們將其設為 false。

最後,我們將從陣列中的鉤子傳回 isCopied 以及 handleCopy。

從「react」匯入React;
從「複製到剪貼簿」匯入副本;

匯出預設函數useCopyToClipboard(resetInterval = null) {
const [isCopied, setCopied] = React.useState(false);

函數handleCopy(text) {
if (typeof text === "string" || typeof text == "number") {
複製(text.toString());
setCopied(true);
} 其他 {
setCopied(假);
控制台.錯誤(
無法將 typeof ${typeof text} 複製到剪貼簿,必須是字串或數字。
);
}
}

返回[isCopied,handleCopy];
}
如何使用useCopyToClipboard
我們現在可以在任何我們喜歡的元件中使用 useCopyToClipboard。

就我而言,我將其與複製按鈕組件一起使用,該組件接收我們的程式碼片段的程式碼。

要實現此功能,我們所需要做的就是在按鈕上新增點擊按鈕。並傳回一個名為句柄複製的函數,其中包含以文字形式請求的程式碼。一旦被複製,那就是真的。我們可以顯示不同的圖標,表示複製成功。

從「react」匯入React;
從“../svg/ClipboardIcon”匯入剪貼簿圖示;
從“../svg/SuccessIcon”導入 SuccessIcon;
從“../utils/useCopyToClipboard”導入 useCopyToClipboard;

函數 CopyButton({ code }) {
const [isCopied, handleCopy] = useCopyToClipboard();

返回(
處理複製(程式碼)}>
{已複製? : }

);
}
如何新增重置間隔
我們可以對程式碼進行一項改進。正如我們目前編寫的鉤子一樣,isCopied 將始終為 true,這意味著我們將始終看到成功圖示:

成功-gif.gif
如果我們想在幾秒鐘後重置狀態,您可以傳遞一個時間間隔來使用CopyToClipboard。讓我們添加該功能。

回到我們的鉤子中,我們可以建立一個名為resetInterval的參數,其預設值為null,這將確保如果沒有參數傳遞給它,則狀態不會重設。

然後我們將添加 useEffect 來表示,如果文字被複製並且我們有一個重置間隔,我們將在該間隔之後使用 setTimeout 將 isCopied 設定回 false。

此外,如果我們的元件正在卸載鉤子(這表示我們的狀態不再需要更新),我們需要清除逾時。

從「react」匯入React;
從「複製到剪貼簿」匯入副本;

匯出預設函數useCopyToClipboard(resetInterval = null) {
const [isCopied, setCopied] = React.useState(false);

const handleCopy = React.useCallback((text) => {
if (typeof text === "string" || typeof text == "number") {
複製(text.toString());
setCopied(true);
} 其他 {
setCopied(假);
控制台.錯誤(
無法將 typeof ${typeof text} 複製到剪貼簿,必須是字串或數字。
);
}
}, []);

React.useEffect(() => {
讓超時;
if (isCopied && 重置間隔) {
timeout = setTimeout(() => setCopied(false), resetInterval);
}
返回() => {
清除超時(超時);
};
}, [isCopied, ResetInterval]);

返回[isCopied,handleCopy];
}
最後,我們可以做的最後一個改進是將handleCopy包裝在useCallback鉤子中,以確保它不會在每次重新渲染時重新建立。

最終結果
這樣,我們就有了最後一個鉤子,它允許在給定的時間間隔後重置狀態。如果我們將一個傳遞給它,我們應該會看到如下所示的結果。

從「react」匯入React;
從“../svg/ClipboardIcon”匯入剪貼簿圖示;
從“../svg/SuccessIcon”導入 SuccessIcon;
從“../utils/useCopyToClipboard”導入 useCopyToClipboard;

函數 CopyButton({ code }) {
// isCopied 在 3 秒超時後重設
const [isCopied, handleCopy] = useCopyToClipboard(3000);

返回(
處理複製(程式碼)}>
{已複製? : }

);
}
最終結果.gif

  1. usePageBottom 掛鉤 在 React 應用程式中,有時了解用戶何時滾動到頁面底部非常重要。

在具有無限滾動功能的應用程式中,例如 Instagram,一旦用戶點擊頁面底部,您就需要獲取更多貼文。

Instagram 中的無限滾動
讓我們看看如何為類似的用例(例如建立無限滾動)自行建立 usePageBottom 鉤子。

我們先在 utils 資料夾中建立一個單獨的檔案 usePageBottom.js,然後加入一個同名的函數(鉤子):

// utils/usePageBottom.js
從“react”導入React;

匯出預設函數 usePageBottom() {}

接下來,我們需要計算使用者何時點擊頁面底部。我們可以透過視窗中的信息來確定這一點。為了存取它,我們需要確保呼叫鉤子的元件已安裝,因此我們將使用帶有有空依賴項數組的 useEffect 鉤子。

// utils/usePageBottom.js
從“react”導入React;

導出預設函數usePageBottom() {
React.useEffect(() => {}, []);
}

當視窗的innerHeight值加上文件的scrollTop值等於offsetHeight時,使用者會捲動到頁面底部。如果這兩個值相等,則結果將為 true,且使用者已捲動至頁面底部:

// utils/usePageBottom.js
從“react”導入React;

導出預設函數usePageBottom() {
React.useEffect(() => {
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight;
}, []);
}

我們將把這個表達式的結果儲存在一個變數 isBottom 中,並且我們將更新一個名為 Bottom 的狀態變量,我們最終將從我們的鉤子中返回它。

// utils/usePageBottom.js
從“react”導入React;

導出預設函數usePageBottom() {
const [bottom, setBottom] = React.useState(false);

React.useEffect(() => {
const isBottom =
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight;
setBottom(isButton);
}, []);

返回底部;
}

但是,我們的程式碼無法正常運作。為什麼不呢?

問題在於,每當使用者滾動時我們都需要計算 isBottom。因此,我們需要使用 window.addEventListener 監聽捲動事件。我們可以透過建立一個每當使用者滾動時呼叫的本地函數來重新評估該表達式,該函數稱為handleScroll。

// utils/usePageBottom.js
從“react”導入React;

導出預設函數usePageBottom() {
const [bottom, setBottom] = React.useState(false);

React.useEffect(() => {
函數handleScroll() {
const isBottom =
window.innerHeight + document.documentElement.scrollTop
=== document.documentElement.offsetHeight;
setBottom(isButton);
}
window.addEventListener("scroll", handleScroll);
}, []);

返回底部;
}

最後,由於我們有一個正在更新狀態的事件偵聽器,因此我們需要處理使用者離開頁面並且元件被刪除的事件。我們需要刪除新增的滾動事件監聽器,這樣我們就不會嘗試更新不再存在的狀態變數。

我們可以透過從 useEffect 和 window.removeEventListener 傳回一個函數來實現這一點,我們在其中傳遞對相同 handleScroll 函數的參考。我們就完成了。

// utils/usePageBottom.js
從“react”導入React;

導出預設函數usePageBottom() {
const [bottom, setBottom] = React.useState(false);

React.useEffect(() => {
函數handleScroll() {
const isBottom =
window.innerHeight + document.documentElement.scrollTop
=== document.documentElement.offsetHeight;
setBottom(isButton);
}
window.addEventListener("scroll", handleScroll);
返回() => {
window.removeEventListener("scroll", handleScroll);
};
}, []);

返回底部;
}

現在我們可以在任何想要知道是否已到達頁面底部的函數中簡單地呼叫此程式碼。

在我的 Gatsby 網站中,我有一個標題,當我減小頁面大小時,我想顯示更少的連結。

調整視窗大小以顯示標題
為此,我們可以使用媒體查詢 (CSS),或者我們可以使用自訂 React hook 來為我們提供頁面的當前大小並隱藏或顯示 JSX 中的連結。

之前,我使用的是一個名為react-use 的函式庫中的鉤子。我決定創建自己的掛鉤來提供視窗的尺寸(寬度和高度),而不是引入整個第三方程式庫。我將這個鉤子稱為 useWindowSize。

如何建立鉤子
首先,我們將在實用程式 (utils) 資料夾中建立一個新檔案 .js,其名稱與鉤子 useWindowSize 相同。我將匯入 React(以使用鉤子),同時匯出自訂鉤子。

// utils/useWindowSize.js

從「react」匯入React;

導出預設函數useWindowSize() {}

現在,由於我在伺服器渲染的 Gatsby 網站中使用它,所以我需要取得視窗的大小。但我們可能無法訪問它,因為我們在伺服器上。

為了檢查並確保我們不在伺服器上,我們可以查看視窗類型是否不等於未定義的字串。

在這種情況下,我們可以返回瀏覽器的預設寬度和高度,例如物件內的 1200 和 800:

// utils/useWindowSize.js

從「react」匯入React;

導出預設函數useWindowSize() {
if (視窗類型!== "未定義") {
回傳 { 寬度:1200,高度:800 };
}
}

如何取得視窗的寬度和高度
假設我們在客戶端並且可以獲得窗口,我們可以使用 useEffect 鉤子透過與視窗互動來執行副作用。我們將包含一個空的依賴項數組,以確保僅在安裝元件(呼叫此鉤子)後才呼叫效果函數。

要找出視窗的寬度和高度,我們可以新增一個事件監聽器並監聽調整大小事件。每當瀏覽器大小改變時,我們可以更新一個狀態(使用 useState 建立),我們稱之為 windowSize,更新它的 setter 將是 setWindowSize。

// utils/useWindowSize.js

從「react」匯入React;

導出預設函數useWindowSize() {
if (視窗類型!== "未定義") {
回傳 { 寬度:1200,高度:800 };
}

const [windowSize, setWindowSize] = React.useState();

React.useEffect(() => {
window.addEventListener("調整大小", () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
});
}, []);
}

調整視窗大小時,將呼叫回調,並將 windowSize 狀態更新為目前視窗尺寸。為此,我們將寬度設定為 window.innerWidth,將高度設定為 window.innerHeight。

如何新增SSR支援
但是,我們這裡的程式碼將無法運作。這是因為鉤子的一個關鍵規則是不能有條件地呼叫它們。因此,在呼叫 useState 或 useEffect 鉤子之前,我們不能在它們之上新增條件。

因此為了解決這個問題,我們將有條件地設定 useState 的初始值。我們將建立一個名為 isSSR 的變量,它將執行相同的檢查以查看視窗是否不等於字串 undefined。

我們將使用三元組來設定寬度和高度,首先檢查我們是否在伺服器上。如果是,我們將使用預設值,如果不是,我們將使用 window.innerWidth 和 window.innerHeight。

// utils/useWindowSize.js

從「react」匯入React;

導出預設函數useWindowSize() {
// if (typeof window !== "undefined") {
// return { 寬度: 1200, 高度: 800 };
// }
const isSSR = typeof window !== "未定義";
const [windowSize, setWindowSize] = React.useState({
寬度: isSSR ? 1200:window.innerWidth,
高度:是SSR? 800:window.innerHeight,
});

React.useEffect(() => {
window.addEventListener("調整大小", () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
});
}, []);
}

最後,我們需要考慮元件何時卸載。我們需要做什麼?我們需要刪除調整大小偵聽器。

如何刪除調整大小事件監聽器
您可以透過從 useEffectand 傳回一個函數來做到這一點。我們將使用 window.removeEventListener 刪除監聽器。

// utils/useWindowSize.js

從「react」匯入React;

導出預設函數useWindowSize() {
// if (typeof window !== "undefined") {
// return { 寬度: 1200, 高度: 800 };
// }
const isSSR = typeof window !== "未定義";
const [windowSize, setWindowSize] = React.useState({
寬度: isSSR ? 1200:window.innerWidth,
高度:是SSR? 800:window.innerHeight,
});

React.useEffect(() => {
window.addEventListener("調整大小", () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
});

return () => {
  window.removeEventListener("resize", () => {
    setWindowSize({ width: window.innerWidth, height: window.innerHeight });
  });
};

}, []);
}

但是我們需要對同一函數的引用,而不是像這裡那樣的兩個不同的函數。為此,我們將為兩個偵聽器建立一個名為changeWindowSize 的共用回呼函數。

最後,在鉤子結束時,我們將返回 windowSize 狀態。就是這樣。

// utils/useWindowSize.js

從「react」匯入React;

導出預設函數useWindowSize() {
const isSSR = typeof window !== "未定義";
const [windowSize, setWindowSize] = React.useState({
寬度: isSSR ? 1200:window.innerWidth,
高度:是SSR? 800:window.innerHeight,
});

函數changeWindowSize() {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
}

React.useEffect(() => {
window.addEventListener("resize",changeWindowSize);

return () => {
  window.removeEventListener("resize", changeWindowSize);
};

}, []);

返回視窗大小;
}

最終結果
要使用該鉤子,我們只需在需要的地方導入它,呼叫它,並在我們想要隱藏或顯示某些元素的地方使用寬度。

就我而言,這是在 500 像素標記處。在那裡,我想隱藏所有其他鏈接,只顯示“立即加入”按鈕,就像您在上面的示例中看到的那樣:

//組件/StickyHeader.js

從「react」匯入React;
從“../utils/useWindowSize”導入 useWindowSize;

函數 StickyHeader() {
const { width } = useWindowSize();

返回(


{/* 僅當視窗大於 500px /}
時可見 {寬度> 500 && (


感言


價格



有問題嗎?


>
)}
{/
在任何視窗大小下都可見 */}


立即加入



);
}

這個鉤子適用於任何伺服器渲染的 React 應用程序,例如 Gatsby 和 Next.js。

  1. useDeviceDetect 鉤子 我正在為我的課程建立一個新的登陸頁面,我在行動裝置上遇到了一個非常奇怪的錯誤。在桌上型電腦上,樣式看起來很棒。

但是當我在手機上查看時,一切都錯位並且損壞了。

反應應用程式錯誤
我將問題追溯到一個名為“react-device-detect”的庫,我用它來檢測用戶是否擁有行動裝置。如果是這樣,我會刪除標題。

// templates/course.js
從“react”導入React;
從“react-device-detect”導入 { isMobile };

函數 Course() {
返回(


{!isMobile && }
{/* 更多組件... */}
>
);
}

問題是這個函式庫不支援伺服器端渲染,而這是 Gatsby 預設使用的。因此,我需要創建自己的解決方案來檢查用戶何時使用行動裝置。為此,我決定建立一個名為 useDeviceDetect 的自訂掛鉤。

我如何建立 Hook
我在 utils 資料夾中為此掛鉤創建了一個具有相同名稱的單獨檔案 useDeviceDetect.js。由於鉤子只是可共享的 JavaScript 函數,它利用了 React 鉤子,因此我建立了一個名為 useDeviceDetect 的函數並導入了 React。

// utils/useDeviceDetect.js
從“react”導入React;

匯出預設函數useDeviceDetect() {}

如何從視窗取得用戶代理
我們可以透過 userAgent 屬性(位於 window 的 navigator 屬性上)來確定是否可以取得有關使用者裝置的資訊。

由於將視窗 API 作為 API/外部資源進行互動會被視為副作用,因此我們需要在 useEffect 掛鉤中存取使用者代理程式。

// utils/useDeviceDetect.js
從“react”導入React;

匯出預設函數useDeviceDetect() {
React.useEffect(() => {
console.log(使用者的裝置是:${window.navigator.userAgent});
// 也可以寫成 'navigator.userAgent'
}, []);
}

元件安裝後,我們可以使用 typeof navigator 來確定我們是在客戶端還是伺服器上。如果我們在伺服器上,我們將無法存取該視窗。 typeof navigator 將等於字串 undefined,因為它不存在。否則,如果我們在客戶端,我們將能夠取得我們的用戶代理屬性。

我們可以使用三元組來表達所有這些來獲取 userAgent 資料:

// utils/useDeviceDetect.js
從“react”導入React;

匯出預設函數useDeviceDetect() {
React.useEffect(() => {
const userAgent =
導航器類型===“未定義”? "" : navigator.userAgent;
}, []);
}

如何檢查userAgent是否為行動裝置
userAgent 是一個字串值,如果使用行動設備,則該值將設定為以下任一個設備名稱:

Android、BlackBerry、iPhone、iPad、iPod、Opera Mini、IEMobile 或 WPDesktop。

我們要做的就是取得得到的字串,並使用 .match() 方法和正規表示式來查看它是否是這些字串中的任何一個。我們將其儲存在一個名為 mobile 的局部變數中。

我們將使用 useState 掛鉤將結果儲存在狀態中,我們將為其指定初始值 false。為此,我們將建立一個對應的狀態變數 isMobile,並且 setter 將為 setMobile。

// utils/useDeviceDetect.js
從“react”導入React;

匯出預設函數useDeviceDetect() {
const [isMobile, setMobile] = React.useState(false);

React.useEffect(() => {
const userAgent =
typeof window.navigator ===「未定義」? "" : navigator.userAgent;
const mobile = 布林值(
userAgent.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
)
);
setMobile(行動裝置);
}, []);
}

因此,一旦我們獲得移動值,我們就會將其設定為狀態。最後,我們將從鉤子中返回一個對象,以便將來如果我們想選擇向此鉤子添加更多功能,則可以添加更多值。

在物件內,我們將新增 isMobile 作為屬性和值:

// utils/useDeviceDetect.js
從“react”導入React;

匯出預設函數useDeviceDetect() {
const [isMobile, setMobile] = React.useState(false);

React.useEffect(() => {
const userAgent =
typeof window.navigator ===「未定義」? "" : navigator.userAgent;
const mobile = 布林值(
userAgent.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
)
);
setMobile(行動裝置);
}, []);

回傳 { isMobile };
}

最終結果
回到登陸頁面,我們可以執行鉤子並簡單地從解構物件中取得該屬性並在我們需要的地方使用它。

// templates/course.js
從“react”導入React;
從“../utils/useDeviceDetect”導入 useDeviceDetect;

函數 Course() {
const { isMobile } = useDeviceDetect();

返回(


{!isMobile && }
{/* 更多組件... */}
>
);
}

結論
正如我試圖透過每個範例來說明的那樣,當第三方函式庫無法滿足要求時,自訂 React hook 可以為我們提供解決我們自己的問題的工具。

我希望本指南能讓您更了解何時以及如何建立自己的 React hooks。請隨意在您自己的專案中使用任何這些鉤子和上述程式碼,並作為您自己的自訂 React 鉤子的靈感。

以上是如何建立自己的 React Hook:逐步指南的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn