You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React自定义useFetch Hook泛型类型适配问题求助

解决useFetch Hook的TypeScript类型定义问题

核心问题分析

当前实现的主要问题是IState<T>中data定义为T | T[],导致调用useFetch<MovieType>时返回的data是MovieType | MovieType[]的联合类型,不符合单一类型的需求。此外还有以下细节问题:

  • 初始状态data: []仅适配数组类型,请求单个对象时类型不匹配
  • useEffect依赖数组为空,url变化时不会重新发起请求
  • 请求开始时未重置error状态,可能保留之前的错误状态
  • axios.get未指定响应类型,导致response.data类型为any

解决方案

调整泛型设计,让T直接对应最终返回的data类型(单个对象或数组),通过可选参数明确是否从response.data.results取数据,同时修正类型定义和请求逻辑:

1. 修正状态接口与初始状态

interface IState<T> {
  data: T | null; // 用null作为通用初始值,适配单个对象/数组两种场景
  loading: boolean;
  error: boolean;
}

const initialState = {
  data: null,
  loading: false,
  error: false,
} as const; // 锁定初始状态类型,避免类型拓宽

2. 重写useFetch Hook

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

function useFetch<T>(url: string, options?: { isList: boolean }) {
  const [state, setState] = useState<IState<T>>(initialState);

  useEffect(() => {
    // 请求开始:重置loading和error状态
    setState(prev => ({ ...prev, loading: true, error: false }));

    axios.get<{ results?: T } | T>(url)
      .then(response => {
        let data: T;
        // 根据isList参数判断取results还是直接取data
        if (options?.isList && 'results' in response.data) {
          data = response.data.results;
        } else {
          data = response.data as T;
        }
        setState(prev => ({ ...prev, loading: false, data }));
      })
      .catch(e => {
        setState(prev => ({ ...prev, loading: false, error: true }));
        if (e instanceof Error) console.error(e.message);
      });
  }, [url, options]); // 加入url和options作为依赖,确保变化时重新请求

  return state;
}

3. 调用示例

  • 请求单个对象:
interface MovieType {
  id: number;
  title: string;
}

const { data, loading, error } = useFetch<MovieType>('/api/movie/1');
// data类型为 MovieType | null
  • 请求对象数组:
const { data, loading, error } = useFetch<MovieType[]>('/api/movies', { isList: true });
// data类型为 MovieType[] | null

额外优化建议

  • 添加取消请求逻辑,避免组件卸载后setState导致内存泄漏:
useEffect(() => {
  const source = axios.CancelToken.source();
  
  setState(prev => ({ ...prev, loading: true, error: false }));
  axios.get<{ results?: T } | T>(url, { cancelToken: source.token })
    .then(/* ... */)
    .catch(e => {
      if (!axios.isCancel(e)) {
        setState(prev => ({ ...prev, loading: false, error: true }));
        if (e instanceof Error) console.error(e.message);
      }
    });

  return () => source.cancel('组件卸载,取消请求');
}, [url, options]);

内容的提问来源于stack exchange,提问作者ntimpt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 13:36:14