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。

相关推荐
计算机魔术师2 小时前
Ethan Mollick 谈点与群:智能体自组织为何让管理假设失效
前端
故七月3 小时前
GEO 信源评估体系:如何判断一条网页信源能否被大模型采信
前端·网络·人工智能
liangshanbo12153 小时前
Webpack 常见 Plugin 面试题
前端·webpack·node.js
xxwl5853 小时前
vue3的入门学习
前端·vue.js·学习
用户847172102843 小时前
.shot 文件格式规范
前端·后端
web打印社区3 小时前
政务办事大厅:叫号小票、办理回执网页怎么静默出纸
前端·javascript·vue.js·pdf·html·政务
C++ 老炮儿的技术栈3 小时前
有符号变量与无符号变量的区别
服务器·开发语言·前端·数据结构·c++·算法·c
aixingpan3 小时前
aixingpan.cn API开发文档:api_docs_bichart_natalvssecondary2接口指南
前端·php
liangshanbo12154 小时前
Webpack Tree Shaking 面试题整理
前端·webpack·node.js
去伪存真4 小时前
从乱码到高精度检索:探矿业务中 TXT、Word、PDF 与网页的 RAG 清洗之道
前端·agent