如何创建适配不同API响应结构的TypeScript通用数据获取Hook?
适配不同API响应结构的通用React数据获取Hook
问题背景
开发电影发现项目时,从TMDB的两个API端点获取数据:
- 电影接口(
/discover/movie)返回数据在results字段中 - 分类接口(
/genre/movie/list)返回数据在genres字段中
原有的useGenres和useMovies存在大量重复逻辑,尝试提取的泛型useData Hook仅默认读取results字段,导致分类接口调用时返回undefined,无法复用。
解决方案
修改通用useData Hook,添加可选参数dataField用于指定从API响应中提取数据的字段名,默认值设为results适配电影接口;调用分类接口时传入genres即可。同时完善TypeScript类型定义,确保类型安全。
修改后的代码
通用useData Hook
import { useEffect, useState } from "react"; import apiClient from "../services/api-client"; import { CanceledError } from "axios"; // 泛型T表示数据项类型,R表示API响应的完整结构 const useData = <T, R = { results: T[] }>( endpoint: string, dataField: keyof R = 'results' as keyof R ) => { const [data, setData] = useState<T[]>([]); const [error, setError] = useState<unknown>(); const [isLoading, setLoading] = useState(false); useEffect(() => { setLoading(true); const controller = new AbortController(); apiClient .get<R>(endpoint, { signal: controller.signal }) .then((res) => { setData(res.data[dataField] as unknown as T[]); setLoading(false); }) .catch((err) => { if (err instanceof CanceledError) return; setLoading(false); setError(err); }); return () => controller.abort(); }, [endpoint, dataField]); return { data, error, isLoading }; }; export default useData;
重构后的useMovies
import useData from "./useData"; export interface Movie { id: number; title: string; backdrop_path: string; vote_average: number; } // 无需指定dataField,默认使用results const useMovies = () => useData<Movie>("/discover/movie"); export default useMovies;
重构后的useGenres
import useData from "./useData"; export interface Genre { id: number; name: string; } // 指定dataField为genres,匹配分类API的响应结构 const useGenres = () => useData<Genre, { genres: Genre[] }>("/genre/movie/list", "genres"); export default useGenres;
API客户端代码(保持不变)
import axios from "axios"; export default axios.create({ baseURL: "https://api.themoviedb.org/3", params: { key: "ABC", }, headers: { accept: "application/json", Authorization: "Bearer XYZ", }, });
说明
- 泛型
R用于定义API响应的完整结构,默认值适配电影接口的{ results: T[] },分类接口则显式指定为{ genres: Genre[] } dataField参数通过keyof R约束,确保传入的字段名一定存在于响应结构中,保证类型安全- 完整保留了原有的请求取消、加载状态、错误处理逻辑,彻底消除代码重复
内容的提问来源于stack exchange,提问作者Osteen Omega
相关产品推荐
相关产品推荐

