TypeScript 中 Type 和 Interface 傻傻分不清?看完这篇就不纠结了

你是不是也有过这样的困惑:

打开一个老项目,或者在做 Code Review 时,发现代码里一会儿是 interface Props,一会儿又是 type State。问同事为什么要混用,他也支支吾吾说不出个所以然,最后只能来一句:"哎呀,反正都能跑,看心情呗。"

但在 TypeScript 的世界里,"能跑"和"写得好"是两码事。

type(类型别名)和 interface(接口)这对双胞胎,在 TS 诞生之初就一直相爱相杀。它们确实太像了,像到在绝大多数 CRUD 业务开发中,你闭着眼随便选一个都不会报错。

但是,作为一个追求代码质量的开发者,我们不能止步于此。

今天,我们跳出表面的语法糖,从底层机制入手,彻底搞清楚它们的本质区别,并给你一套最佳实践方案。拒绝选择困难症。

1. 表象:90% 的重合度与其误区

为什么大家会纠结?因为在定义"对象"的形状(Shape)时,它们长得几乎一模一样。

看看下面的代码,你能一眼看出区别吗?

typescript 复制代码
// 使用 interface
interface UserI {
  name: string;
  age: number;
}

// 使用 type
type UserT = {
  name: string;
  age: number;
}

在日常使用中,如果你想实例化一个对象,或者在函数参数中约束类型,这俩兄弟的表现是完全一致的。它们都支持:

  • 定义对象结构
  • 定义函数签名
  • 支持泛型
  • 支持类(Class)的实现(implements)

但这正是新手容易陷入的误区:以为它们是可以随意互换的同义词。 实际上,TS 设计这两个概念,是为了解决完全不同的问题。

2. 核心:声明合并、类型表达与扩展性

区别不仅存在,而且在关键时刻决定了你的架构设计是否合理。主要体现在以下三个核心维度:

2.1 声明合并 (Declaration Merging) ------ Interface 的必杀技

这是 interface 独有的特性,也是它存在的最大理由。

场景模拟:

你引入了一个第三方库(比如 Vue 或 jQuery),但你发现它的全局对象上少了一个你需要的属性。这时,如果你用 interface,你可以直接在自己的代码里"补"上这个属性。

kotlin 复制代码
// 假设这是第三方库定义的 interface
interface User {
  name: string;
}

// 你的代码中再次定义同名 interface
interface User {
  age: number;
}

// ✨ TS 会自动把它们缝合在一起!
const me: User = {
  name: "Gemini",
  age: 18 // 必须两个属性都有,否则报错
};

反观 type:

它是封闭的(Closed)。一旦定义,无法通过同名方式修改。

ini 复制代码
type User = {
  name: string;
};

// ❌ 报错:Duplicate identifier 'User'.
type User = {
  age: number;
};

💡 结论: interface 具有开放性,允许后续扩展;而 type 具有封闭性,更适合确定的业务逻辑。

2.2 类型表达能力 ------ Type 的主场

type 的全称是 Type Alias(类型别名) 。既然是别名,它就能给任何东西起名字,不仅仅是对象。

在处理复杂类型时,Type 的灵活性完胜 Interface:

  • 联合类型 (Union Types): 前端开发中最常用的功能。

    ini 复制代码
    type Status = 'pending' | 'success' | 'failed';
    type ID = string | number;

    Interface 无法直接定义这种"或"的关系。

  • 元组 (Tuple):

    ini 复制代码
    type Point = [number, number];
  • 类型体操:

    当你使用 Pick、Omit、Record 或者条件类型(Conditional Types)时,产出的结果通常都是 type。

2.3 扩展方式:Extends vs Intersection

虽然两者都能实现"继承"的效果,但语义不同。

  • Interface 使用 extends:侧重于面向对象的层级继承。
  • Type 使用 & (交叉类型) :侧重于集合的合并。

虽然通常可以互通,但在处理冲突属性时,interface 会直接报错提醒,而交叉类型(&)可能会产生 never 类型,导致错误提示不够直观。


3. 规范:一套拿来即用的最佳实践

讲了这么多理论,回到最初的问题:我们在项目中到底该怎么选?

与其每次都纠结,不如遵循这套简单的 "二选一法则" ,这也符合目前主流大厂(如 Google 规范)和 React 社区的推荐趋势:

场景一:你在编写库 (Library) 或第三方包

请优先使用 interface

理由: 作为库的作者,你需要为你的用户留出"后路"。用户可能需要利用"声明合并"的特性,向你的全局接口中注入自定义属性(比如扩展 Window 对象或给 Request 对象增加 user 字段)。使用 Interface 是对使用者的尊重。

场景二:你在编写业务应用 (Application / UI 组件)

请优先使用 type

理由:

  1. 一致性 (Consistency): 既然 type 能搞定对象、联合类型、元组等所有情况,而 interface 只能搞定对象,那么全员使用 type 可以让代码风格更统一。
  2. 安全性 (Safety): 在业务代码中,我们通常不希望定义好的类型被莫名其妙地"自动合并"了(这是隐患)。type 的报错提醒能让你更安全。
  3. React 生态: 现在的 React 社区更倾向于用 type 来定义 PropsState,因为它在处理组件复合类型时更加直观。

总结

为了方便记忆,我做了一张对比速查表:

特性 Interface Type
核心理念 描述对象的形状 (Shape) 任何类型的别名 (Alias)
声明合并 支持 (自动合并) ❌ 不支持 (会报错)
**联合类型 ( )** ❌ 不支持
映射/条件类型 ❌ 不支持 ✅ 支持
最佳使用场景 编写库 (Library) 编写应用 (App)

一句话口诀:

对外 API(库)用 Interface,对内业务逻辑用 Type。如果你实在拿不准,就用 Type,直到你必须用 Interface 为止。

你们团队的代码规范里,是强制用 type 还是 interface?还是像大部分项目一样"随缘混用"?

欢迎在评论区留言,我们一起聊聊 TS 里的那些坑!

相关推荐
佛系打工仔3 小时前
绘制K线第二章:背景网格绘制
android·前端·架构
Irene19914 小时前
Vue3 TypeScript 项目中,Emits 验证的使用场景
typescript·vue3·验证
明天好,会的4 小时前
分形生成实验(五):人机协同破局--30万token揭示Actix-web状态管理的微妙边界
运维·服务器·前端
C_心欲无痕5 小时前
nginx - alias 和 root 的区别详解
运维·前端·nginx
我是苏苏7 小时前
Web开发:C#通过ProcessStartInfo动态调用执行Python脚本
java·服务器·前端
无羡仙7 小时前
Vue插槽
前端·vue.js
用户6387994773058 小时前
每组件(Per-Component)与集中式(Centralized)i18n
前端·javascript
SsunmdayKT8 小时前
React + Ts eslint配置
前端
开始学java8 小时前
useEffect 空依赖 + 定时器 = 闭包陷阱?count 永远停在 1 的坑我踩透了
前端