介紹
當您開發 React 元件時,您可能會發現檔案成長到數百行。當 useState、useEffect、API 查詢和驗證邏輯與渲染標記混合時,就會發生這種情況,使元件難以讀取和維護。
這種關注點的混合使得編寫單元測試變得困難,並且在跨不同視圖重複使用邏輯時增加了複製貼上錯誤的風險。
在 React 中將 UI(表示)與業務邏輯(行為)分開的最佳方法是建立 自訂 Hooks。本指南概述了建立可重複使用的自訂掛鉤的核心設計指南。
1. 什麼是自訂 Hook?
自訂掛鉤是一個標準 JavaScript 函數,其名稱 以前綴 use 開頭。在自訂掛鉤中,您可以呼叫標準 React 掛鉤,例如 useState、useEffect 和 useContext。
定制掛鉤的優點
- **乾淨的元件(關注點分離):**元件嚴格專注於聲明視覺化 UI 結構(JSX),而資料擷取、狀態更新和副作用則封裝在鉤子內。
- 邏輯可重複使用性: 在多個同級元件之間共用輔助邏輯(如視窗大小觀察器、表單管理或取得請求包裝器),而無需重複程式碼。
2.重構範例:提取自訂hook
讓我們來看一個範例:一個從 API 取得使用者清單並將其顯示在螢幕上的元件。
重構前(UI 與邏輯混合)
import { useState, useEffect } from 'react';
export function UserList() {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://api.example.com/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setIsLoading(false);
})
.catch(err => {
setError(err);
setIsLoading(false);
});
}, []);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error occurred.</p>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
- 問題: 此元件處理 API URL、請求邏輯、錯誤追蹤、載入指示器和 JSX 渲染。這使得獨立測試 UI 和資料擷取邏輯變得困難。
重構後(提取 useUsers)
首先,將資料取得邏輯隔離到一個單獨的檔案中,作為名為 useUsers 的自訂掛鉤:
// useUsers.js
import { useState, useEffect } from 'react';
export function useUsers() {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let active = true;
fetch('https://api.example.com/users')
.then(res => res.json())
.then(data => {
if (active) {
setUsers(data);
setIsLoading(false);
}
})
.catch(err => {
if (active) {
setError(err);
setIsLoading(false);
}
});
// Clean up to prevent race conditions
return () => { active = false; };
}, []);
// Return only the data and states required by components
return { users, isLoading, error };
}
現在,更新元件以使用自訂掛鉤:
// UserList.jsx
import { useUsers } from './useUsers';
export function UserList() {
// Access data and state via the custom hook
const { users, isLoading, error } = useUsers();
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error occurred.</p>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
- 結果: 元件現在變得精簡且可讀。它不需要知道 API 請求是如何配置的,您可以為 UI 元件和自訂掛鉤編寫獨立的測試。
3. 設計自訂 Hook 的三個規則
規則 1:不傳回 JSX(HTML 標籤)
自訂掛鉤應該管理邏輯狀態,而不是返回可視元件。傳回物件、陣列、變數或回呼函數而不是 JSX 元素(如 <div />),以保持鉤子的靈活性。
規則 2:保持返回介面較小
僅傳回元件管理其渲染生命週期所需的變數。避免傳回鉤子內宣告的每個內部狀態變數。
規則 3:巢狀和組合 Hook
您可以在您的掛鉤中呼叫其他自訂掛鉤。例如,您可以在 useUsers 內部呼叫通用 useFetch 掛鉤,以進一步模組化您的程式碼。
結論
React 自訂掛鉤有助於保持元件的可讀性和可測試性:
- **將資料取得和狀態管理邏輯提取到以
use為前綴的函數中。 ** - **封裝副作用和取得狀態,僅傳回 UI 元件所需的變數。 **
- **處理鉤子內的清理邏輯,以防止記憶體洩漏和競爭條件。 **
採用這些自訂鉤子設計模式來建立更易於維護的 React 應用程式。

