深入理解 TypeScript 接口:不仅要会写,更要会读懂和封装

深入理解 TypeScript 接口(Interface):不仅要会写,更要会读懂和封装

学 TypeScript 接口(interface)不能只停留在"怎么写"的阶段:

typescript 复制代码
interface User {
  id: number;
  name: string;
}

但在真实项目中,你会遇到远不止如此简单的定义:

  • 来自后端接口返回的嵌套结构;
  • 通用组件的类型抽象
  • 多接口继承、条件类型;
  • 维护别人写的复杂接口文件......

本篇文章从工程实践出发,不谈晦涩的语法大全,专注解决以下问题:

接口该怎么定义?怎么封装?怎么阅读与复用?

一、接口是类型化思维的桥梁

typescript 复制代码
// 后端返回结构(接口文档):
{
  "code": 0,
  "msg": "success",
  "data": {
    "user": {
      "id": 1,
      "name": "Alice"
    }
  }
}

// 定义类型
interface User {
  id: number;
  name: string;
}

interface APIResponse<T = any> {
  code: number;
  msg: string;
  data: T;
}

type GetUserResponse = APIResponse<{ user: User }>;

批注:

  • 类型名建议统一使用 ResponseDTOVOPropsForm 等后缀,增强语义清晰度。
  • APIResponse<T> 是泛型封装的经典写法,提升接口复用能力。
  • 通常配合通用请求封装函数函数使用
ts 复制代码
async function fetchData<T>(url: string): Promise<APIResponse<T>> {
  const res = await axios.get(url);
  return res.data;
}

二、如何阅读别人定义的接口?

很多时候我们不是自己定义接口,而是"维护和理解"已有接口,尤其是在多人协作项目中。 常见的场景包括:

  • 在别人的接口基础上修改字段或补充功能;
  • 模仿已有接口的定义规范,扩展新的接口类型;
  • 理解已有接口的命名和结构方式,避免重复造轮子。

组件类型继承结构

ts 复制代码
interface BaseProps {
  className?: string;
}

interface ButtonProps extends BaseProps {
  onClick: () => void;
  type?: 'primary' | 'default';
}

阅读技巧:

  • 找"根类型"(BaseProps)看它定义了什么通用能力;
  • 注意是否带有 条件类型联合类型泛型参数
  • 多看命名中的暗示:Props / Config / State 等结尾通常与组件状态或配置相关。
  • 遇到冗长或嵌套较深的类型时,可使用 VS Code 的悬浮查看、跳转定义功能快速定位。

三、如何封装"复用性强"的接口?

场景:统一分页类型结构

ts 复制代码
interface PageParams {
  page: number;
  pageSize: number;
}

interface PageResponse<T> {
  total: number;
  list: T[];
}

// 使用:
function getUserList(): Promise<PageResponse<User>> {
  return http.get('/api/user/list');
}

工程价值:

  • 所有分页接口都可以复用 PageParamsPageResponse<T>
  • 函数签名中明确返回的数据类型结构,开发体验提升;
  • 对测试和 mock 数据生成也很友好。

衍生封装:

ts 复制代码
function getList<T>(url: string, params: PageParams): Promise<PageResponse<T>> {
  return http.get(url, { params });
}

四、从接口拆出"组件契约"

组件设计中的类型约束

ts 复制代码
// 用户选择器组件的 Props
interface UserSelectProps {
  value?: number[];
  onChange?: (value: number[]) => void;
  disabled?: boolean;
}

拆解原则:

  • 明确哪些是必须传入的 props,哪些是选填的(用 ? 标注);
  • 函数类型参数(如 onChange)明确参数和返回值;
  • 类型名建议为 xxxProps,保持一致性;

项目实践建议:

  • 将组件 Props 拆出单独文件:如 UserSelect/type.ts
  • 复杂组件内部的事件回调类型也应统一抽离封装,方便他人使用;
  • 结合泛型、联合类型等提高组件适配能力(如 <T = number> 的通用组件)。

五、组合接口与条件类型技巧

typescript 复制代码
type WithLoading<T> = T & { loading: boolean };

interface User {
  name: string;
}
type UserWithLoading = WithLoading<User>; // { name: string; loading: boolean }

应用场景:

  • 适合对任意数据结构添加 UI 层状态字段(如 loading、selected 等);
  • 多用于表单状态管理、Hook 状态返回值封装。

六、如何保持项目整洁?

  1. 拆成多个小接口再组合,复杂项目一般拆解的比较细
  2. 每个模块独立维护type.ts,按业务模块组织接口定义
  3. interface命名要规范
  4. 使用Pick、Omit做接口裁剪,提高接口灵活性与准确性

示例:

ts 复制代码
// 从完整接口中提取部分字段
interface User {
  id: number;
  name: string;
  email: string;
}

// 用于只展示部分信息的场景
type UserPreview = Pick<User, 'id' | 'name'>;

// 去除不需要的字段
type UserWithoutEmail = Omit<User, 'email'>;

参考资料与拓展阅读

欢迎评论区分享你的接口封装经验,或者遇到的"神仙类型定义"!

相关推荐
星光不问赶路人2 小时前
project references在tsserver内工作流程
typescript·visual studio code
keep_di20 小时前
05-vue3+ts中axios的封装
前端·vue.js·ajax·typescript·前端框架·axios
RoyLin1 天前
SurrealDB - 统一数据基础设施
前端·后端·typescript
濮水大叔1 天前
Node生态中最优雅的数据库事务处理机制
typescript·nodejs·nestjs
Mintopia1 天前
如何用 TypeScript 折腾出全排列之你不知道的 :“分布式条件类型”、“递归处理”
前端·javascript·typescript
-D调定义之崽崽2 天前
【初学】使用 node 编写 MCP Server
typescript·node·mcp
濮水大叔2 天前
VonaJS提供的读写分离,直观,优雅🌼
typescript·nodejs·nestjs
RoyLin2 天前
命名实体识别
前端·后端·typescript
Ares-Wang3 天前
Vue3 》》vite》》TS》》封装 axios ,Promise<T>
vue.js·typescript