Featured image of post 設計乾淨的 React 自訂 Hook 以實現可重複使用性Featured image of post 設計乾淨的 React 自訂 Hook 以實現可重複使用性

設計乾淨的 React 自訂 Hook 以實現可重複使用性

了解如何使用 React Custom Hooks (useXXX) 將業務邏輯和狀態管理與視圖層分離,以提高程式碼可維護性。

介紹

當您開發 React 元件時,您可能會發現檔案成長到數百行。當 useStateuseEffect、API 查詢和驗證邏輯與渲染標記混合時,就會發生這種情況,使元件難以讀取和維護。

這種關注點的混合使得編寫單元測試變得困難,並且在跨不同視圖重複使用邏輯時增加了複製貼上錯誤的風險。

在 React 中將 UI(表示)與業務邏輯(行為)分開的最佳方法是建立 自訂 Hooks。本指南概述了建立可重複使用的自訂掛鉤的核心設計指南。


1. 什麼是自訂 Hook?

自訂掛鉤是一個標準 JavaScript 函數,其名稱 以前綴 use 開頭。在自訂掛鉤中,您可以呼叫標準 React 掛鉤,例如 useStateuseEffectuseContext

定制掛鉤的優點

  • **乾淨的元件(關注點分離):**元件嚴格專注於聲明視覺化 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 自訂掛鉤有助於保持元件的可讀性和可測試性:

  1. **將資料取得和狀態管理邏輯提取到以 use 為前綴的函數中。 **
  2. **封裝副作用和取得狀態,僅傳回 UI 元件所需的變數。 **
  3. **處理鉤子內的清理邏輯,以防止記憶體洩漏和競爭條件。 **

採用這些自訂鉤子設計模式來建立更易於維護的 React 應用程式。