魯斯前端布魯斯前端

文章中英模式

布魯斯前端JS面試題目 - 實作自定義 useFetch Hook

學習如何實作 React 中的自定義 useFetch Hook,掌握數據獲取、加載狀態管理、錯誤處理及快取機制,提升前端應用的用戶體驗。

影片縮圖

懶得看文章?那就來看影片吧

自定義 useFetch Hook 概述

在 React 應用中,數據獲取是一個常見需求。自定義 useFetch Hook 可以封裝數據獲取邏輯,包括加載狀態、錯誤處理,甚至可以實現快取和請求去重等高級功能,從而簡化組件代碼並提高可復用性。

為何需要自定義 useFetch Hook?

  • 1. 關注點分離:將數據獲取邏輯與視圖邏輯分開
  • 2. 代碼復用:避免在多個組件中重複編寫相似的數據獲取邏輯
  • 3. 一致性處理:統一處理加載狀態、錯誤和響應
  • 4. 功能擴展:可以方便地添加快取、防抖、節流等功能
  • 5. 測試簡化:使組件和數據獲取邏輯更容易獨立測試

基本的 useFetch Hook 實作

下面是一個基本的 useFetch Hook 實作,包含數據獲取、加載狀態和錯誤處理:

import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // 重置狀態
    setLoading(true);
    setData(null);
    setError(null);
    
    // 獲取數據
    fetch(url)
      .then(response => {
        // 檢查響應狀態
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        return response.json();
      })
      .then(json => {
        setData(json);
        setLoading(false);
      })
      .catch(err => {
        setError(err.message);
        setLoading(false);
      });
  }, [url]); // 當 URL 變化時重新獲取數據

  return { data, loading, error };
}

// 使用示例
function UserProfile({ userId }) {
  const { data, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!data) return null;

  return (
    <div>
      <h1>{data.name}</h1>
      <p>Email: {data.email}</p>
    </div>
  );
}

這個基本實作提供了三個關鍵狀態:數據、加載狀態和錯誤信息,使組件能夠根據這些狀態渲染不同的 UI。

取消請求的 useFetch 實作

在實際應用中,當組件卸載或依賴項變化時,我們需要取消進行中的請求以避免內存洩漏和狀態更新錯誤。以下是一個使用 AbortController 實現請求取消的 useFetch Hook:

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // 創建 AbortController 實例
    const controller = new AbortController();
    const signal = controller.signal;
    
    // 重置狀態
    setLoading(true);
    setData(null);
    setError(null);
    
    // 獲取數據
    fetch(url, { signal })
      .then(response => {
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        return response.json();
      })
      .then(json => {
        setData(json);
        setLoading(false);
      })
      .catch(err => {
        // 忽略因取消請求而產生的錯誤
        if (err.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          setError(err.message);
          setLoading(false);
        }
      });
    
    // 清理函數:組件卸載或依賴項變化時取消請求
    return () => {
      controller.abort();
    };
  }, [url]);

  return { data, loading, error };
}

這個改進版本使用了 AbortController API 來取消進行中的請求。當組件卸載或 URL 變化時,清理函數會自動執行,取消尚未完成的請求,有效防止內存洩漏和在已卸載組件上設置狀態的問題。