ToolkitX
知识库工具箱

接口与类型别名

interface, type, 继承, 扩展

25min·进阶

01. interface vs type——用哪个

interface 和 type 都能描述对象形状,大部分情况下可以互换。但在 TS 的哲学里它们有分工: interface——用于定义对象的结构,可以被扩展(extends)、可以被同名的 interface 合并(declaration merging)。适合公开的 API 类型、第三方库的类型扩展。 type——用于类型别名,可以是任意类型(联合、交叉、函数、基本类型的别名)。不能同名的 type(报错),但可以用交叉类型 & 组合。 选 interface 还是 type: - 需要被外部扩展(如某个库的类型让用户补字段)→ interface - 需要联合类型、映射类型这类复杂类型 → type - 只是一个普通对象类型 → 两者都可以,团队内部统一风格更重要
typescript
// interface——对象结构
interface User {
  id: number;
  name: string;
}

// type——任何类型
type ID = string | number;
type Point = { x: number; y: number };
type Callback = (data: User) => void;

// interface 可以被多次声明(合并)
interface Config {
  url: string;
}
interface Config {
  timeout: number;  // 自动合并
}
// Config = { url: string; timeout: number }

02. interface 的继承与扩展

interface 使用 extends 继承另一个 interface。可以被多个接口共同继承(多重继承)。 extends 后面可以接多个 interface 用逗号分隔:interface Child extends Parent1, Parent2。 interface 也可以 extends 一个 type(只要这个 type 是对象类型)。同样 type 可以通过交叉类型 & 跟 interface 组合。 实际项目中常见场景:基础 interface 定义核心字段,子 interface 扩展额外字段。比如 BaseEntity(id、createdAt、updatedAt)被所有实体 interface 继承。
typescript
// 基础实体
interface BaseEntity {
  id: number;
  createdAt: Date;
  updatedAt: Date;
}

// 继承
interface User extends BaseEntity {
  name: string;
  email: string;
}

// 多重继承
interface Timestamped { createdAt: Date; updatedAt: Date; }
interface SoftDeletable { deletedAt?: Date; }
interface Post extends Timestamped, SoftDeletable {
  id: number;
  title: string;
}

// interface extends type
type WithId = { id: number };
interface Product extends WithId {
  name: string;
}

03. 可选属性、只读属性与索引签名

可选属性——属性名后加 ?,表示这个字段可能不存在。常用于 API 的 PATCH 请求参数(只有要改的字段才传)。 只读属性——readonly 前缀,初始化后不能修改。跟 const 的区别:const 用于变量,readonly 用于属性。 索引签名——定义未知键名的属性类型。{ [key: string]: any } 表示这个对象可以有任意字符串键。 Record<K, V> 是索引签名的泛型版——Record<string, User> 等价于 { [key: string]: User }。 泛型索引签名——{ [K in keyof T]: T[K] } 遍历 T 的所有属性创建新类型,这是映射类型的本质。
typescript
// 可选属性
interface UpdateUserDTO {
  name?: string;
  email?: string;
  // 只传要改的字段,不改的不用传
}

// 只读属性
interface Config {
  readonly apiUrl: string;
  readonly timeout: number;
}

// 索引签名
interface Dictionary<T> {
  [key: string]: T;
}
get users: Dictionary<User> = {};
users['user-1'] = { id: 1, name: 'Alice' };
索引签名配合 readonly 可以做只读字典:{ readonly [key: string]: Value }。这在做配置对象映射时很有用。

04. 函数类型与 call signature

interface 不仅能描述对象,还能描述函数——这叫 call signature。语法:(参数列表): 返回值类型。 既有属性又能调用的对象——函数本身也是对象可以有属性,用 interface 同时描述函数的调用签名和属性。 函数类型还可以用 type 定义:type Fn = (x: number) => string。type 更简洁适合简单函数,interface 适合复杂函数(有重载、有属性)。
typescript
// call signature
interface Greeting {
  (name: string): string;
}
const greet: Greeting = (name) => 'Hello, ' + name;

// 既有属性又能调用的对象
interface Logger {
  (message: string): void;
  level: 'info' | 'warn' | 'error';
}

const logger: Logger = (msg) => console.log(msg);
logger.level = 'info';
logger('test');   // 可以调用

// 函数重载的 interface
get OverloadedFn = {
  (x: string): string;
  (x: number): number;
};
函数类型用 type 更常见:type Handler = (req: Request, res: Response) => void。interface 的函数签名在需要重载或带属性时更合适。

05. 接口设计最佳实践

1. 接口应该小而专注——Interface Segregation(接口隔离原则)。一个大接口拆成多个小接口再组合。 2. 使用可选属性表示「可能没有」的字段而不是 null | undefined。? 比 | undefined 更清晰。 3. 用 extends 而不是重复声明相同字段。多个接口有公共字段应该提取基接口。 4. 对外暴露的接口和内部使用的接口分开。API 返回给前端的跟数据库实体通常不同。 5. 接口文件名——很多项目用 .type.ts 或 .interface.ts 或直接在 entity 文件里定义。按团队约定统一。
typescript
// 好的做法:小接口组合
interface HasId { id: number; }
interface HasTimestamps { createdAt: Date; updatedAt: Date; }
interface HasName { name: string; }

interface User extends HasId, HasTimestamps, HasName {
  email: string;
}

// API 返回给前端 vs 数据库实体
interface UserEntity extends HasId, HasTimestamps {
  name: string;
  passwordHash: string;  // 数据库有
}

interface UserResponse {
  id: number;
  name: string;
  // 没有 passwordHash——不给前端
}

知识测验

1/5正确 0

interface 和 type 什么时候必须用 interface?

下一节

泛型

下一节