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

如何创建适配不同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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 04:53:12