TypeScript中interface与type的终极对比指南

在 TypeScript 中,interfacetype 都用于定义类型,但它们在设计目的、语法和功能上有明显差异。以下是它们的核心区别:


1. ​基本概念

  • ​**interface**

    专门用于定义 ​对象类型 的形状(Object Shape),强调结构描述,适合描述类、对象、函数等结构。

    typescript 复制代码
    typescript
    interface User {
      name: string;
      age: number;
    }
  • ​**type**

    类型别名,可为任何类型(包括原始类型、联合类型、元组等)命名,用途更广泛。

    typescript 复制代码
    typescript
    type ID = string | number; // 联合类型
    type Point = [number, number]; // 元组
    type Callback = () => void; // 函数类型

2. ​声明合并(Declaration Merging)​

  • ​**interface**

    支持同名声明自动合并,适合扩展第三方类型或逐步定义类型。

    kotlin 复制代码
    typescript
    interface User { name: string; }
    interface User { age: number; }
    // 合并为 { name: string; age: number; }
  • ​**type**

    同名重复声明会报错,无法合并。

    ini 复制代码
    typescript
    type User = { name: string }; // ❌ Error: Duplicate identifier
    type User = { age: number };

3. ​继承与扩展

  • ​**interface**

    使用 extends 继承其他接口,语法更直观。

    php 复制代码
    typescript
    interface Animal { name: string; }
    interface Dog extends Animal { bark(): void; }
  • ​**type**

    使用交叉类型 & 实现类似继承的效果,更灵活。

    ini 复制代码
    typescript
    type Animal = { name: string };
    type Dog = Animal & { bark(): void };

4. ​适用场景

  • ​**interface 专长**

    • 定义对象结构(如类、函数、对象)。
    • 需要声明合并(如扩展第三方类型)。
    • 类实现接口(implements)。
  • ​**type 专长**

    • 定义联合类型、元组、映射类型等复杂类型。
    • 使用条件类型(extends ? :)、模板字面量类型等高级特性。
    • 为其他类型提供别名(包括原始类型)。
    ini 复制代码
    typescript
    type Status = "success" | "error"; // 字面量联合类型
    type Keys = keyof User; // 映射类型

5. ​其他区别

特性 interface type
函数类型定义 支持对象语法和函数重载 只能直接定义函数签名
动态属性 无法直接使用动态计算属性 可使用映射类型(in)动态生成属性
类实现 支持 implements 若为对象类型,也能被类实现
扩展性 更适合通过声明合并逐步扩展 扩展需重新定义或使用交叉类型

映射类型(Mapped Types)

在 TypeScript 中,​映射类型(Mapped Types)​ 是一种基于现有类型动态生成新类型的高级特性。它可以通过 in 关键字遍历一个联合类型的键集合,并为每个键生成新的属性类型。这是 type 独有的能力,interface 无法直接实现类似操作。


核心概念:用 in 动态生成属性

映射类型的基本语法为:

ini 复制代码
typescript
type NewType = {
  [Key in Keys]: ValueType;
};
  • **Key**:遍历的键(变量名,可以是任意标识符,如 K)。
  • **Keys**:一个联合类型(例如 "name" | "age"),表示要遍历的键集合。
  • **ValueType**:每个键对应的值的类型(可以是固定类型,或基于原类型的动态类型)。

具体示例
1. 基本动态属性生成

假设有一个联合类型 Keys,基于它生成一个对象类型,每个属性的值类型为 string

ini 复制代码
typescript
type Keys = "name" | "age" | "email";

type DynamicObject = {
  [K in Keys]: string; // 遍历 Keys 中的每个键,值为 string
};

// 等价于:
type DynamicObject = {
  name: string;
  age: string;
  email: string;
};
2. 基于现有类型转换

映射类型常与 keyof 结合,动态操作现有类型的所有属性:

ini 复制代码
typescript
interface User {
  name: string;
  age: number;
  email: string;
}

// 将 User 的所有属性转换为可选属性
type OptionalUser = {
  [K in keyof User]?: User[K]; 
};

// 等价于:
type OptionalUser = {
  name?: string;
  age?: number;
  email?: string;
};
3. 更复杂的类型操作

可以结合条件类型、模板字面量等特性,实现更动态的转换:

typescript 复制代码
typescript
// 将 User 的所有属性名改为 `getXxx` 格式,且值为函数
type Getters = {
  [K in keyof User as `get${Capitalize<K>}`]: () => User[K];
};

// 等价于:
type Getters = {
  getName: () => string;
  getAge: () => number;
  getEmail: () => string;
};

为什么 interface 无法实现?

interface 的设计初衷是声明对象的结构 ,而不是动态生成类型。它无法直接遍历联合类型或使用 in 动态生成属性。例如:

csharp 复制代码
typescript
// ❌ 错误:interface 不支持映射类型语法
interface DynamicInterface {
  [K in "name" | "age"]: string;
}

实际应用场景
  1. 属性修饰符操作

    快速生成 ReadonlyPartialRequired 等工具类型:

    ini 复制代码
    typescript
    type ReadonlyUser = Readonly<User>; // 所有属性变为只读
    type PartialUser = Partial<User>;    // 所有属性变为可选
  2. 类型裁剪或转换

    修改属性名或值的类型:

    ini 复制代码
    typescript
    // 将 User 的所有属性值改为 boolean
    type BooleanUser = {
      [K in keyof User]: boolean;
    };
  3. 筛选特定属性

    结合条件类型过滤属性:

    ini 复制代码
    typescript
    // 只保留 User 中的字符串类型属性
    type StringProps = {
      [K in keyof User as User[K] extends string ? K : never]: User[K];
    };
    // 等价于 { name: string; email: string }

总结
  • **type + 映射类型**:通过 in 动态遍历联合类型的键集合,生成新的属性,适合复杂类型操作。
  • **interface**:静态定义对象结构,无法动态生成属性。

当需要基于现有类型动态生成或修改属性时,映射类型是 type 的杀手级特性,而 interface 无法替代这一能力。


6. ​错误提示

  • **interface**
    错误提示更清晰,直接指出缺少或多余的属性。
  • **type**
    复杂类型(如联合类型)的错误提示可能较冗长。

7. ​如何选择?

  • 默认使用 interface:定义对象结构、需要声明合并时。
  • 使用 type:需要联合类型、元组、高级类型操作时。

两者在多数场景下可以互换,但理解差异能帮助写出更清晰的类型代码。

8. ​总结

对比维度 ​**interface** ​**type**
基本用途 定义对象类型(类、函数、对象的结构) 为任何类型命名(包括原始类型、联合类型、元组、函数等)
声明合并 ✅ 支持同名合并(多次声明自动合并) ❌ 禁止同名重复声明
继承语法 extends 关键字(直观继承接口) 交叉类型 &(通过 & 合并类型)
动态属性生成 ❌ 不支持映射类型(无法用 in 动态生成属性) ✅ 支持映射类型(通过 in 遍历键集合生成属性)
适用场景 对象结构定义、声明合并、类实现(implements 联合类型、元组、条件类型、模板字面量类型、复杂类型操作
函数类型定义 支持对象语法和函数重载: { (x: number): number } 只能直接定义函数签名: type Fn = (x: number) => number
工具类型支持 需结合 extends 或工具类型间接操作 直接支持 PartialReadonlyPick 等工具类型
扩展性 适合逐步扩展(通过声明合并) 需重新定义或交叉类型扩展
错误提示 更清晰(直接指出属性缺失或冗余) 复杂类型(如联合类型)可能提示冗长
类实现 直接通过 implements 实现 若为对象类型,也能被类实现
示例 interface User {<br> name: string;<br>} type User = {<br> name: string;<br>}
映射类型示例 ❌ 不可用 type Optional<T> = {<br> [K in keyof T]?: T[K];<br>}
相关推荐
申朝先生18 分钟前
用CSS画一条0.5px的线
前端·javascript·css
雪碧聊技术1 小时前
element-plus中Autocomplete自动补全输入框组件的使用
前端·javascript·vue.js·自动补全输入框·autocomplete
浪遏1 小时前
当你向面试官朗诵单例模式时 ,ta说talk is cheep , show me the code🤡
前端·设计模式·面试
zczlsy112 小时前
webpack介绍
前端·webpack·node.js
六个点2 小时前
关于vue的面试考点总结🤯
前端·vue.js·面试
浪遏3 小时前
今晚揭开单例模式的面纱~
前端·设计模式·面试
驯龙高手_追风3 小时前
谷歌Chrome或微软Edge浏览器修改网页任意内容
前端·chrome·edge
luckyext4 小时前
Postman发送GET请求示例及注意事项
前端·后端·物联网·测试工具·小程序·c#·postman
小满zs4 小时前
React第三十章(css原子化)
前端·react.js
一直在学习的小白~4 小时前
前端项目中创建自动化部署脚本,用于 Jenkins 触发 npm run publish 来完成远程部署
前端·自动化·jenkins