鲁斯前端布鲁斯前端

文章中英模式

布鲁斯前端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 变化时,清理函数会自动执行,取消尚未完成的请求,有效防止内存泄漏和在已卸载组件上设置状态的问题。