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

