侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1825 篇文章
  • 累计收到 0 条评论

实现一个带缓存、防抖和错误处理的自定义数据请求Hook

2025-12-12 / 0 评论 / 5 阅读

题目

实现一个带缓存、防抖和错误处理的自定义数据请求Hook

信息

  • 类型:问答
  • 难度:⭐⭐

考点

useState, useEffect, 自定义Hook设计, 异步处理, 性能优化

快速回答

实现一个自定义Hook useFetch 需满足:

  • 支持GET请求数据并缓存结果
  • 输入参数变化时自动重新请求
  • 实现500ms防抖避免频繁请求
  • 返回包含数据/加载状态/错误的对象

核心实现要点:

  1. 使用useState管理数据/加载/错误状态
  2. useEffect处理异步请求和清理
  3. 通过AbortController取消未完成请求
  4. 使用useRef存储缓存和防抖计时器
## 解析

需求分析

需要创建一个可复用的自定义Hook,用于处理数据请求场景,需满足:

  • 缓存机制:相同URL请求直接返回缓存数据
  • 防抖控制:URL频繁变化时延迟500ms再发起请求
  • 错误处理:捕获网络错误并更新状态
  • 竞态处理:确保返回最新请求结果

代码实现

import { useState, useEffect, useRef } from 'react';

const CACHE = {};

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

  // 使用ref存储防抖计时器和最新请求标识
  const debounceRef = useRef(null);
  const abortRef = useRef(null);

  useEffect(() => {
    // 防抖处理:清除前一个计时器
    if (debounceRef.current) {
      clearTimeout(debounceRef.current);
    }

    // 空URL处理
    if (!url) {
      setData(null);
      return;
    }

    // 检查缓存
    if (CACHE[url]) {
      setData(CACHE[url]);
      return;
    }

    // 设置防抖延迟请求
    debounceRef.current = setTimeout(async () => {
      try {
        // 取消未完成的请求
        if (abortRef.current) {
          abortRef.current.abort();
        }

        abortRef.current = new AbortController();
        setLoading(true);
        setError(null);

        const response = await fetch(url, {
          signal: abortRef.current.signal
        });

        if (!response.ok) {
          throw new Error(`请求失败: ${response.status}`);
        }

        const result = await response.json();

        // 更新缓存和状态
        CACHE[url] = result;
        setData(result);
      } catch (err) {
        // 忽略被取消的请求错误
        if (err.name !== 'AbortError') {
          setError(err.message);
          setData(null);
        }
      } finally {
        setLoading(false);
        abortRef.current = null;
      }
    }, 500);

    // 清理函数:取消请求和计时器
    return () => {
      if (debounceRef.current) {
        clearTimeout(debounceRef.current);
      }
      if (abortRef.current) {
        abortRef.current.abort();
      }
    };
  }, [url]);

  return { data, loading, error };
}

原理说明

  • 缓存机制:使用模块级变量存储缓存,相同URL直接返回数据
  • 防抖实现:通过setTimeoutclearTimeout控制请求触发频率
  • 竞态处理AbortController确保总是获取最新请求结果
  • 状态管理:三组state分别跟踪数据/加载/错误状态

最佳实践

  • 缓存策略:可扩展为带过期时间的缓存(如CACHE[url] = { data, timestamp }
  • 参数处理:支持POST请求时可增加options参数
  • 错误分类:根据HTTP状态码返回更具体的错误信息
  • 内存管理:大型应用建议使用LRU缓存策略

常见错误

  • 未处理组件卸载:未在清理函数中取消请求会导致Can't perform state update on unmounted component警告
  • 缺少竞态处理:连续快速切换参数时可能返回旧请求结果
  • 缓存滥用:对动态数据接口使用缓存导致数据过期
  • 防抖失效:未正确清除前一个计时器导致多次请求

扩展知识

  • SWR/React Query:推荐使用成熟的数据请求库处理复杂场景
  • useReducer:当状态逻辑复杂时可替代多个useState
  • TypeScript集成:为Hook添加泛型支持useFetch<TData>(url)
  • 性能优化:结合useCallback记忆化回调函数