核心共同点:定义对象/结构的形状
type 和 interface 最常见的用途都是用来描述一个对象应该包含哪些属性、方法以及它们的类型。
// 使用 interface 定义
interface PointInterface {
x: number;
y: number;
}
// 使用 type 定义
type PointType = {
x: number;
y: number;
};
// 使用它们来注解变量
let p1: PointInterface = { x: 10, y: 20 };
let p2: PointType = { x: 30, y: 40 };
// 它们都可以描述可选属性、只读属性和方法
interface UserInterface {
readonly id: number;
name: string;
email?: string; // 可选
greet(): string;
}
type UserType = {
readonly id: number;
name: string;
email?: string; // 可选
greet: () => string; // 方法的类型定义方式略有不同,但效果一样
};
interface (接口)详解
-
主要用途: 定义对象、类(通过 implements)的结构契约。
-
语法: 使用 interface 关键字。
interface InterfaceName { propertyName: Type; optionalProperty?: Type; readonly readonlyProperty: Type; methodName(param: Type): ReturnType; } -
特点:
-
擅长描述对象形状: 这是它的核心强项。
-
可继承 (extends) : 一个接口可以继承一个或多个其他接口,合并它们的成员。
interface Animal { name: string; } interface Dog extends Animal { breed: string; } // Dog 继承 Animal let myDog: Dog = { name: "Buddy", breed: "Golden" }; -
可被类实现 (implements) : 类可以使用 implements 关键字来确保它符合某个接口定义的契约。
interface ClockInterface { currentTime: Date; } class DigitalClock implements ClockInterface { currentTime: Date = new Date(); } -
声明合并 (Declaration Merging) : 这是 interface 最独特的特性之一。如果你在同一个作用域内定义了多个同名的 interface,它们会自动合并成一个单一的接口。这对于扩展已有的接口(甚至是第三方库的接口)非常有用。
// 第一次声明 interface Box { height: number; width: number; } // 第二次声明 (在同一作用域或通过模块扩展) interface Box { scale: number; // height: string; // Error: Subsequent property declarations must have the same type. } // 合并后的 Box 接口等同于: // interface Box { // height: number; // width: number; // scale: number; // } let box: Box = { height: 5, width: 6, scale: 10 };
-
type (类型别名)详解
-
主要用途: 给任何类型起一个新名字(别名)。
-
语法: 使用 type 关键字和 = 赋值。
type AliasName = ExistingType; -
特点:
-
极其灵活: type 可以为任何类型创建别名,包括:
- 原始类型: type MyString = string;
- 联合类型: type ID = string | number;
- 交叉类型: type PointAndColor = PointType & { color: string };
- 元组类型: type PointTuple = [number, number];
- 函数类型: type MathFunc = (x: number, y: number) => number;
- 对象字面量类型 (如上面 PointType 示例)
- 甚至其他类型别名: type UserID = ID;
-
常用于联合和交叉类型: type 在定义联合类型 (|) 和交叉类型 (&) 时比 interface 更直接方便。
type StringOrNumber = string | number; type ClickableAndFocusable = Clickable & Focusable; // Clickable, Focusable 可能是 interface 或 type -
可用于映射类型和条件类型: TypeScript 的高级类型工具(如 Partial, Readonly, Pick<T, K>, 条件类型 T extends U ? X : Y 等)通常与 type 一起使用来创建复杂的新类型。
type PartialPoint = Partial<PointType>; // { x?: number; y?: number; } type StringKeys<T> = { [K in keyof T]: T[K] extends string ? K : never }[keyof T]; -
不能声明合并: 如果你尝试定义同名的 type,TypeScript 会抛出错误,它不会像 interface 那样合并。
type BoxType = { height: number; }; // type BoxType = { width: number; }; // Error: Duplicate identifier 'BoxType'.
-
核心区别总结
| 特性 | interface (接口) | type (类型别名) |
|---|---|---|
| 主要用途 | 定义对象/类的结构契约 | 为任何类型起别名 |
| 适用范围 | 主要用于对象形状、类实现 | 原始类型、联合、交叉、元组、对象、函数等所有类型 |
| 声明合并 | 支持 (多个同名接口会自动合并) | 不支持 (同名 type 会报错) |
| 继承/扩展 | 使用 extends 关键字 (可继承多个) | 使用交叉类型 & 来组合对象形状 |
| 类实现 | 使用 implements 关键字直接实现 | 类可以实现 type 定义的对象形状,但 implements 关联性不如 interface 强 |
| 定义复杂类型 | 相对受限 | 非常灵活,常用于联合、交叉、映射、条件类型等 |
何时使用 interface vs 何时使用 type?(实践建议)
这是一个在 TypeScript 社区中有些争议但也有普遍共识的话题:
-
定义公共 API 的对象形状 (例如库的类型定义) :
- 推荐使用 interface。主要原因是 interface 的声明合并特性允许库的使用者在需要时安全地扩展接口(例如,给全局 Window 接口添加属性)。
-
定义应用程序内部的对象形状:
- 两者皆可。选择哪个更多是团队风格和一致性的问题。
- 如果你预期未来可能需要合并声明,或者更倾向于面向对象的 extends 和 implements 语法,可以选择 interface。
- 如果你更喜欢 type 的灵活性,或者需要定义联合、交叉等非对象形状,或者想明确表示“这个类型定义不应该被外部合并”,可以选择 type。
-
定义联合类型、交叉类型、元组、函数类型、原始类型别名:
- 必须使用 type。interface 无法做到这些。
-
使用 TypeScript 高级类型工具 (映射类型、条件类型等) :
- 通常使用 type。这些高级类型操作的结果往往需要用 type 来命名。
-
保持一致性:
- 在一个项目或团队内部,对于定义对象形状,尽量保持一致。如果大部分地方用了 interface,就继续用 interface (除非需要 type 的特定功能);反之亦然。
总结:
- interface 专注于描述对象和类的“契约”,并拥有独特的“声明合并”能力。
- type 是一个更通用的“类型别名”工具,适用于所有类型,尤其擅长处理联合、交叉和利用高级类型工具。
两者在定义对象形状方面有很多重叠,选择哪个往往取决于具体需求(是否需要合并?是否需要定义非对象类型?)以及团队的编码规范。理解它们的核心区别(尤其是声明合并和适用范围)是做出正确选择的关键。