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
相关产品推荐
相关产品推荐

