
| 能力 | interface |
type |
说明 | 详细示例 |
|---|---|---|---|---|
| 描述对象形状 | ✅ | ✅ | 两者等价,基本功能 | interface Foo { a: string } · type Foo = { a: string } |
| 声明合并(同名自动合并) | ✅ ⭐ | ❌ | 扩展第三方库类型的关键(模块增强) | 见下方示例① |
被 extends 继承 |
✅ | ✅(& 模拟) |
interface 报错更早更清晰 | 见下方示例② |
| **联合类型 `A | B`** | ❌ | ✅ ⭐ | type 独占 |
| 元组/数组 | ❌(只能间接) | ✅ ⭐ | type 独占 | type Point = [number, number] |
| 映射类型/条件类型 | ❌ | ✅ ⭐ | type 独占 | 见下方示例③ |
| 原始类型别名 | ❌ | ✅ | type 独占 | type ID = string |
实现 implements |
✅ | ✅(对象字面量类型) | class 可实现两者 | 见下方示例④ |
| 性能(超大类型) | 略优 | 略差 | interface 有"惰性求值"优化 | --- |
| 声明泛型 | ✅ | ✅ | 都能定义泛型 | interface Box<T> { v: T } · type Box<T> = { v: T } |
| 递归引用 | ✅ | ✅ | 都能引用自身 | 见下方示例⑤ |
| 索引签名 | ✅ | ✅ | 两者都支持 | 见下方示例⑥ |
| 函数类型描述 | ✅(调用签名) | ✅(函数类型表达式) | 语法不同 | 见下方示例⑦ |
| 构造函数签名 | ✅ | ✅ | 两者都支持 | 见下方示例⑧ |
| 模板字面量类型 | ❌ | ✅ | type 独占 | type EventName = \on${string}`` |
| 类型守卫效果 | 相同 | 相同 | 都可用于 instanceof/类型收窄 | --- |
this 类型 |
✅ | ✅ | 两者都支持 | interface Fn { (this: T): void } |
每个能力的详细示例
① 声明合并(Declaration Merging)------ interface 独占
TypeScript
// ✅ interface:同名自动合并成员
interface User { id: number }
interface User { name: string } // 合并成功,User 现在有 id + name
interface User { email?: string } // 再合并,加上可选 email
// 最终效果等价于:
// interface User { id: number; name: string; email?: string }
// 实战场景:为第三方库扩展类型(如 Express Request)
// express.d.ts
declare namespace Express {
interface Request { // 同名合并,给 Request 加字段
currentUser?: { id: string; role: 'admin' | 'user' }
}
}
// ❌ type:同名重复声明直接报错
type User2 = { id: number }
// type User2 = { name: string } // ❌ Duplicate identifier 'User2'
② 继承/组合------冲突处理差异
TypeScript
// interface 使用 extends
interface A { id: string }
interface B { id: number }
interface C extends A, B {}
// ❌ 立即报错:Named property 'id' of types 'string' and 'number' are not identical
// 错误发生在定义处(早失败,清晰)
// type 使用 &
type D = A & B
// ⚠️ 不报错,但 D['id'] 变成 string & number = never
declare const d: D
d.id = 'x' // ❌ 报错:Type 'string' is not assignable to type 'never'
// 错误推迟到使用处(晚失败,错误信息难读)
// interface 多继承时属性覆盖规则
interface X { a: string; b: number }
interface Y { b: string; c: boolean }
// interface Z extends X, Y { }
// ❌ 报错:b 的类型冲突(string vs number),哪怕 b 是兼容的也会报错
③ 映射类型与条件类型------ type 独占
TypeScript
// 映射类型
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
// 条件类型 + infer
type ElementOf<T> = T extends (infer E)[] ? E : never
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never
// 分发条件类型
type Exclude<T, U> = T extends U ? never : T
// 递归类型
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}
// 联合 + 条件组合
type Nullable<T> = T | null
type Serializable<T> = T extends Date ? string : T
// ⚠️ interface 无法实现上述任何写法
④ implements ------ 两者都可用
// interface 被 implements
interface IAnimal {
name: string
makeSound(): void
}
class Dog implements IAnimal {
name = 'dog'
makeSound() { console.log('bark') }
}
// type 被 implements(对象字面量类型)
type TAnimal = {
name: string
makeSound(): void
}
class Cat implements TAnimal {
name = 'cat'
makeSound() { console.log('meow') }
}
⑤ 递归引用
// interface 递归
interface TreeNode {
value: number
children?: TreeNode[] // 递归引用自身
}
// type 递归
type TreeNode2 = {
value: number
children?: TreeNode2[]
}
// 交叉类型递归
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue } // 递归 + 索引签名
⑥ 索引签名
// interface 的索引签名
interface StringMap {
[key: string]: string
}
// type 的索引签名
type StringMap2 = {
[key: string]: string
}
// 带具体属性 + 索引签名(两者行为完全一致)
interface Config {
host: string // 具体属性
port: number
[key: string]: string | number // 索引签名必须兼容具体属性
}
// ⚠️ 索引签名陷阱:interface 和 type 都一样
interface Bad {
host: string
[key: string]: number // ❌ 报错:string 不能赋给 number
}
⑦ 函数类型描述------语法差异
// 方式一:type 的函数类型表达式(最常用)
type Handler = (e: MouseEvent) => void
// 方式二:interface 的调用签名(可附带属性)
interface DescribableFn {
(x: number): string // 调用签名
description: string // 额外属性
}
// 方式三:type 也可实现"函数+属性"
type DescribableFn2 = {
(x: number): string
description: string
}
// 使用
const fn: DescribableFn2 = ((x: number) => String(x)) as DescribableFn2
fn.description = 'converts number to string'
⑧ 构造函数签名
// interface 表示构造函数
interface Ctor {
new (name: string, age: number): object
}
// type 表示构造函数
type Ctor2 = new (name: string, age: number) => object
// 使用:约束工厂函数
function createInstance(Ctor: Ctor, name: string, age: number) {
return new Ctor(name, age)
}
// 另一个例子:描述类本身
type Class<T> = new (...args: any[]) => T
function factory<T>(Ctor: Class<T>, ...args: any[]): T {
return new Ctor(...args)
}
技巧:判断何时用 interface vs type
当满足以下任一条件时,优先用 interface:
- 描述的是"对象形状"
- 需要被他人扩展(声明合并)
- 希望报错信息更清晰(如继承冲突早暴露)
当满足以下任一条件时,必须用 type:
- 需要联合类型
| - 需要元组/数组
- 需要映射类型、条件类型、infer
- 需要模板字面量类型
- 需要给原始类型起别名
一句话结论: 能用 interface 描述对象形状就用它;需要"类型运算"时必须用 type。