首页 / TypeScript 入门教程 / interface vs type:选型指南

TypeScript 入门教程

interface vs type:选型指南

本教程共 80 篇 · 第 28 篇 · 更新于 2026-08-10 · 约 10 分钟阅读

TypeScriptTypeScript 入门教程接口类型别名interface vs type

本节目标:理清 interface 和 type 的本质差异,建立一个”什么时候用哪个”的判断框架,不再纠结于这个问题。

“interface 和 type 到底有什么区别?“——这大概是 TypeScript 社区被问得最多的问题之一。如果你去搜,会看到大量争论,有人站 interface、有人站 type,也有人觉得”都一样随便用”。

真相是:它们大部分时候确实可以互换,但在特定场景下各有优劣。 这一章的任务不是制造宗教战争,而是帮你建立一个清晰的判断框架。

先看一张全景对比图

维度interfacetype
定义对象形状✅ 原生设计✅ 可以
函数类型✅ 调用签名 { (): R }✅ 箭头函数风格
联合类型✅ 核心优势
元组✅ 核心优势
原始类型别名
映射类型✅ 重点场景
条件类型✅ 重点场景
声明合并✅ 核心优势
extends 冲突检测✅ 编译时立即报错❌ 延迟到 never
extends 继承❌ 用 & 替代
类 implements
递归引用

现在逐条展开看这些差异在实际代码中意味着什么。

interface 的独门绝技

声明合并

这是 interface 和 type 最根本的差异。interface 同名声明会自动合并:

interface User {
  name: string;
}

// 另一个地方(可能是另一个文件、另一段代码)
interface User {
  age: number;
}

// 最终类型:{ name: string; age: number }
const u: User = { name: "Alice", age: 30 };

而 type 会直接报”重复标识符”错误。

Tip

声明合并是一个强大的机制,但不建议在日常业务代码中刻意依赖它来拼凑类型。它的正当用途主要是”给第三方库的类型声明打补丁”(module augmentation),或者 TypeScript 标准库内部用来扩展内置类型(比如每次新标准给 Array 加方法时)。

extends 属性冲突即时检测

第 25 章已经演示过:当 extends 的两个父接口有同名但类型不兼容的属性时,编译直接报错。而用 & 做交叉类型时,不兼容的属性类型会被悄悄收窄成 never,直到你尝试构造对象时才暴露问题。

这不是”谁好谁坏”的问题,而是两种设计哲学:

  • extends 偏向 fail-fast——尽早告诉你出问题了。
  • & 偏向 容忍——先接受,用的时候再判断。

对于定义对象形状这件事,fail-fast 通常更友好。

对”对象描述”有优化

TypeScript 编译器内部对 interface 有专门优化——检查对象是否匹配 interface 的速度比检查 type 更快。这个差异在绝大多数项目里感知不到,但如果你的类型定义量非常大(比如大型 Monorepo),interface 确实会快那么一点。TypeScript 官方团队也明确表示 interface 是描述对象的首选。

type 的独门绝技

联合类型和元组

// 这些 interface 完全做不了
type Status = "pending" | "success" | "error";
type Point3D = [number, number, number];
type Mixed = string | { code: number; message: string };

联合类型和元组是 type 最常用的场景。如果你的类型需要”要么这样、要么那样”的语义,那 type 是唯一选择。

映射类型

type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Partial<T> = { [K in keyof T]?: T[K] };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };

映射类型只能通过 type 定义。虽然 TypeScript 内置了很多工具类型(PartialReadonlyPick 等等),但理解底层原理能让你在需要自定义映射时不至于束手无策。

条件类型

type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">;  // true
type B = IsString<42>;       // false

条件类型是 TypeScript 类型系统的”if-else”,也是只能通过 type 定义。条件类型配合泛型和 infer 关键字,可以实现非常复杂的类型逻辑——不过那属于进阶内容了。

社区的演进:从”interface 优先”到”各取所需”

早期 TypeScript(2014-2019 年左右)的社区共识非常明确:能用 interface 就用 interface,除非你需要 type 的专属能力。

这个共识的来源很合理:

  • 那时候 type 的功能还没那么丰富。
  • interface 的错误信息更好读。
  • 官方文档也推荐 interface。

但随着 TypeScript 发展,type 的能力不断增强,很多团队(尤其是 React 社区)开始全面使用 type。他们的理由也很充分:

  • type 语法更简洁(不需要分号,可以用 &|)。
  • 在一个文件里统一用 type,不需要同时学两套语法。
  • type 能做所有事情,不需要纠结”这个场景该用哪个”。

我们的建议:TypeScript 7.0 选型策略

回到 2026 年的 TypeScript 7.0,结合当前生态和最佳实践,我们的建议是:

策略一:能用 interface 优先(推荐大多数团队)

  • 描述纯对象形状和类的契约时优先用 interface
  • 需要联合类型、元组、映射类型、条件类型时用 type
  • 对外暴露的公共 API 用 interface(方便消费者通过声明合并扩展)。

这条策略简单好记,适合多成员协作的团队——因为规则清晰,不会出现”这边用 type、那边用 interface”的风格混战。

策略二:全用 type(适合偏好一致性的小团队)

  • 如果你觉得两条规则也是一种心智负担,那全部用 type 也不是不行。
  • 代价是牺牲声明合并能力和 extends 的即时冲突检测。

不推荐的策略:全用 interface。 因为 interface 做不了联合类型、元组、映射类型,你最后一定还是需要 type——所以”全用 interface”是个伪命题。

一句话总结

// 对象形状 → interface
interface User {
  name: string;
  age: number;
}

// 联合 / 元组 / 映射 / 条件 → type
type Status = "active" | "inactive";
type Point = [number, number];
type PartialUser = Partial<User>;

团队协作中的实际考量

如果你的团队已经有既定规范(比如 ESLint 的 @typescript-eslint/consistent-type-definitions 规则),那就遵守团队规范。技术选型的价值在于一致性——团队里所有人都按同一套规则写,读代码的成本最低。

如果你是从零开始定规范,建议这样配置:

{
  "rules": {
    "@typescript-eslint/consistent-type-definitions": ["error", "interface"]
  }
}

这条规则强制所有对象类型定义使用 interface。那遇到必须用 type 的情况怎么办?放心——这条规则只检查可互换的场景(纯对象形状),当你写了联合类型或元组时它不会拦你。

小结

  • interface 和 type 在描述纯对象形状时可以互换,但背后的机制不同。
  • interface 的优势:声明合并、extends 冲突即时检测、编译器优化。
  • type 的优势:联合类型、元组、映射类型、条件类型。
  • 推荐策略:“能用 interface 优先,需联合 / 映射 / 条件时用 type”。
  • 核心原则:团队一致性 > 个人偏好。遵守项目既定规范比纠结哪个”更好”重要得多。

搞懂了选型逻辑,最后一章我们来聊聊声明合并——这个让 interface 如此独特的机制到底是怎么运作的,以及为什么它既是利器也是陷阱。