interface vs type:到底该用哪个

能力 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

相关推荐
敲代码的嘎仔1 小时前
从零实现视频续播 + 学习进度统计:前端心跳、条件更新、GROUP BY 统计全链路拆解
java·前端·数据库·学习·面试·职场和发展·音视频
做萤石二次开发的哈哈1 小时前
海康移动交通四款设备技能接入实战:布控球+取证终端+测速仪+出入口终端,Web/App/小程序移动交通应用快速生成
前端·物联网·萤石开放平台·蓝海aiot一站式工作台·aiot开发·移动交通
IT_陈寒1 小时前
我TM竟然被Java的空指针坑了第三次!
前端·人工智能·后端
xcLeigh1 小时前
AI 编程学习路线图:一份覆盖前端、后端、全栈的系统学习计划
前端·人工智能·学习
linux_cfan2 小时前
HTML5 视频交互标注实践:用开源播放器 ZWPlayer 实现热区、测验与分支节点(附接入代码)
前端·javascript·音视频
小羊没烦恼!2 小时前
Office文件的奥秘——.NET平台下不借助Office实现Word、Powerpoint等文件的解析(完)
java·大数据·前端·网络·word·powerpoint·.net
风骏时光牛马2 小时前
AI提示词工程:高效构建精准指令的方法论
前端
葡萄城技术团队2 小时前
一个好的 BI Dashboard 到底应该怎么设计?
前端
晴天164 小时前
前端 postMessage 使用场景
前端·javascript·网络