← 返回导航

统一 API 响应类型

业务类型 · 实战

场景说明

前后端对接时,接口响应结构需要统一约定:成功时返回业务数据,失败时返回错误信息且不带 data。用 TypeScript 把这套约定建模为"成功/失败的联合类型",并配合 success 字面量字段做可辨识联合,可以在调用方用 if 收窄类型,自动区分有 data 与无 data 两种形态,杜绝访问空 data 的隐患。

完整代码


      
// 统一 API 响应类型:成功与失败的联合,data 泛型参数化
// 用 success 字面量字段做可辨识联合,便于类型收窄

// 成功响应:携带 data
interface SuccessResponse<T> {
  code: number;
  message: string;
  data: T;
  success: true;   // 字面量类型,判别字段
}

// 错误响应:无 data
interface ErrorResponse {
  code: number;
  message: string;
  data: null;
  success: false;
}

// 联合:一个响应要么成功要么失败
type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;

// 应用:请求函数返回联合类型,调用方用 success 收窄
async function request<T>(url: string): Promise<ApiResponse<T>> {
  const res = await fetch(url).then(r => r.json());
  return res as ApiResponse<T>;
}

interface User { id: number; name: string; }

// 调用方:用 success 字段做类型收窄
async function main() {
  const res = await request<User>('/api/user/1');
  if (res.success) {
    // 此处 res 收窄为 SuccessResponse<User>
    console.log(res.data.name);  // 合法:data 是 User
  } else {
    // 此处 res 收窄为 ErrorResponse
    console.log(res.message);    // 合法:错误信息
    // res.data.name; // 报错:data 为 null
  }
}

关键点解析

使用示例与类型推导


      
// 不同接口复用同一响应结构
type UserResp = ApiResponse<User>;
type ListResp = ApiResponse<User[]>;

// 封装一个统一的"取 data 或抛错"工具
function unwrap<T>(res: ApiResponse<T>): T {
  if (res.success) return res.data;
  throw new Error(res.message);
}
const u = unwrap({ code: 0, message: 'ok', data: { id: 1, name: 'wjs' }, success: true });
// => u: User

// 类型守卫函数:便于在多处复用收窄逻辑
function isSuccess<T>(res: ApiResponse<T>): res is SuccessResponse<T> {
  return res.success;
}
// 用 if (isSuccess(res)) 替代 if (res.success),逻辑更内聚
在 Playground 中尝试