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

基于NextJS 13 App Router的TypeScript Async/await Fetch实现是否合理?

你的TypeScript Fetch封装正确性及优化建议

你的基础Fetch封装在TypeScript + Next.js 13环境下是正确可用的,核心的泛型类型约束、HTTP方法枚举、基础错误处理逻辑都符合规范。不过存在一些可以完善的遗漏点,下面逐一说明:

一、当前实现的正确点

  • 用枚举HTTPMethods统一管理HTTP方法,避免硬编码字符串
  • 泛型<TResponse>确保返回值类型安全,和业务类型(如ProductsType)结合能获得完整的TypeScript类型提示
  • 基础的错误处理:当响应状态非2xx时抛出包含状态码和描述的错误

二、遗漏点与优化建议

1. 请求配置扩展性不足

  • 问题:当前的_get、_post只固定了请求方法,未支持headers、credentials、cache等常用配置;同时_post的TBody约束为BodyInit,如果传入普通JSON对象会直接报错(因为BodyInit不包含普通对象)。
  • 优化:
    • 给请求方法添加可选的自定义配置参数,合并到基础配置中
    • 自动处理JSON对象的序列化,避免手动调用JSON.stringify

2. 错误处理不够完整

  • 问题:仅抛出状态码和statusText,但很多API会在非2xx响应中返回包含具体错误信息的JSON(如400返回{error: "参数格式错误"}),当前实现会丢失这些关键信息。
  • 优化:在抛出错误前尝试解析响应的错误数据,将具体错误信息加入错误提示。

3. 类型定义缺失

  • 问题:page.tsx中使用了ProductsType,但types.d.ts仅定义了ProductType,存在类型未定义的问题。
  • 优化:补充ProductsType的类型定义。

4. Next.js 13环境适配优化

  • 问题:如果在Server Components中使用,fetch默认会缓存请求结果,若需要实时数据则需手动配置缓存策略;如果在Client Components中使用,当前实现未处理loading、错误状态的UI反馈。
  • 优化:
    • 在请求配置中添加cache选项(如cache: 'no-store')控制缓存
    • Client Components中可结合React状态管理实现loading、错误状态处理

三、优化后的代码示例

utils.ts

enum HTTPMethods {
  GET = "GET",
  POST = "POST",
  PUT = "PUT",
  PATCH = "PATCH",
  DELETE = "DELETE",
}

async function handleError(response: Response) {
  let errorMessage = `${response.status}: ${response.statusText}`;
  try {
    // 尝试解析API返回的错误详情
    const errorData = await response.json();
    errorMessage = `${errorMessage} - ${errorData.message || JSON.stringify(errorData)}`;
  } catch {
    // 非JSON格式响应,保持原错误信息
  }
  throw new Error(errorMessage);
}

async function request<TResponse>(
  url: string,
  config: RequestInit = {}
): Promise<TResponse> {
  // 合并默认配置与自定义配置
  const mergedConfig: RequestInit = {
    headers: {
      "Content-Type": "application/json",
      ...config.headers,
    },
    ...config,
  };

  // 自动序列化JSON类型的请求体
  if (mergedConfig.body && typeof mergedConfig.body === "object" && !(mergedConfig.body instanceof FormData)) {
    mergedConfig.body = JSON.stringify(mergedConfig.body);
  }

  const response = await fetch(url, mergedConfig);
  if (!response.ok) {
    await handleError(response);
  }

  // 处理无返回值的响应(如204 No Content)
  if (response.status === 204) {
    return {} as TResponse;
  }

  return await response.json();
}

export async function _get<TResponse>(url: string, config?: Omit<RequestInit, "method">): Promise<TResponse> {
  return request<TResponse>(url, { ...config, method: HTTPMethods.GET });
}

export async function _post<TBody, TResponse>(
  url: string,
  body: TBody,
  config?: Omit<RequestInit, "method" | "body">
): Promise<TResponse> {
  return request<TResponse>(url, { ...config, method: HTTPMethods.POST, body });
}

types.d.ts

type ProductType = {
  id: number;
  title: string;
  description: string;
  price: number;
  // other properties
};

// 补充列表类型定义
type ProductsType = ProductType[];

page.tsx(Server Components示例)

import { _get } from '@/utils';
import type { ProductsType } from '@/types';

// 若API返回的是{ products: ProductsType }结构
const { products } = await _get<{ products: ProductsType }>(
  `${process.env.CLIENT_API_URL}/products`,
  { cache: 'no-store' } // 禁用缓存,获取实时数据
);

// 若API直接返回ProductType数组
// const products = await _get<ProductsType>(`${process.env.CLIENT_API_URL}/products`);

// 渲染逻辑
return (
  <div>
    {products.map(product => (
      <div key={product.id}>{product.title}</div>
    ))}
  </div>
);

内容的提问来源于stack exchange,提问作者Bert Van Hecke

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 08:01:06