深入理解 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 }>;
批注:
- 类型名建议统一使用
Response
、DTO
、VO
、Props
、Form
等后缀,增强语义清晰度。 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');
}
工程价值:
- 所有分页接口都可以复用
PageParams
与PageResponse<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 状态返回值封装。
六、如何保持项目整洁?
- 拆成多个小接口再组合,复杂项目一般拆解的比较细
- 每个模块独立维护
type.ts
,按业务模块组织接口定义 - interface命名要规范
- 使用Pick、Omit做接口裁剪,提高接口灵活性与准确性
示例:
ts
// 从完整接口中提取部分字段
interface User {
id: number;
name: string;
email: string;
}
// 用于只展示部分信息的场景
type UserPreview = Pick<User, 'id' | 'name'>;
// 去除不需要的字段
type UserWithoutEmail = Omit<User, 'email'>;
参考资料与拓展阅读
欢迎评论区分享你的接口封装经验,或者遇到的"神仙类型定义"!