类型与接口的区别
问题
TypeScript 中 type 和 interface 有什么区别?什么时候用 type,什么时候用 interface?
答案
type(类型别名)和 interface(接口)都可以定义对象类型,但有一些关键区别。
相同点
定义对象类型
// interface
interface UserInterface {
name: string;
age: number;
}
// type
type UserType = {
name: string;
age: number;
};
// 使用方式完全相同
const user1: UserInterface = { name: 'Alice', age: 25 };
const user2: UserType = { name: 'Bob', age: 30 };
定义函数类型
// interface
interface FuncInterface {
(x: number, y: number): number;
}
// type
type FuncType = (x: number, y: number) => number;
const add1: FuncInterface = (x, y) => x + y;
const add2: FuncType = (x, y) => x + y;
扩展/继承
// interface 继承 interface
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// type 继承 type(使用交叉类型)
type AnimalType = {
name: string;
};
type DogType = AnimalType & {
breed: string;
};
// interface 继承 type
type PersonType = {
name: string;
};
interface Employee extends PersonType {
employeeId: number;
}
// type 继承 interface
interface CarInterface {
brand: string;
}
type ElectricCar = CarInterface & {
batteryCapacity: number;
};
实现类
interface Printable {
print(): void;
}
type Loggable = {
log(message: string): void;
};
// 类可以实现 interface 和 type
class Document implements Printable, Loggable {
print(): void {
console.log('Printing...');
}
log(message: string): void {
console.log(message);
}
}
不同点
1. 声明合并(Declaration Merging)
interface 支持声明合并,type 不支持:
// interface 声明合并
interface User {
name: string;
}
interface User {
age: number;
}
interface User {
email: string;
}
// 三个声明合并为一个
const user: User = {
name: 'Alice',
age: 25,
email: 'alice@example.com'
};
// type 不能重复声明
type Person = {
name: string;
};
// type Person = { // 错误:Duplicate identifier 'Person'
// age: number;
// };
声明合并常用于扩展第三方库的类型,如扩展 Window、Express.Request 等。
2. 基本类型别名
type 可以定义基本类型、联合类型、元组等,interface 不能:
// 基本类型别名
type ID = string | number;
type Name = string;
// 联合类型
type Status = 'pending' | 'active' | 'inactive';
type Result = Success | Error;
// 元组
type Point = [number, number];
type NamedTuple = [name: string, age: number];
// 字面量类型
type Greeting = 'hello' | 'hi' | 'hey';
// interface 无法做到以上这些
// interface ID = string | number; // 语法错误
3. 计算属性
type 可以使用 in 关键字进行映射类型:
type Keys = 'firstName' | 'lastName';
// 映射类型
type Person = {
[K in Keys]: string;
};
// 等价于 { firstName: string; lastName: string }
// interface 不支持映射类型
// interface PersonInterface {
// [K in Keys]: string; // 语法错误
// }
4. 条件类型
只有 type 可以使用条件类型:
type NonNullable<T> = T extends null | undefined ? never : T;
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// interface 不支持条件类型
5. 工具类型实现
内置工具类型都是用 type 实现的:
// Partial 的实现
type Partial<T> = {
[P in keyof T]?: T[P];
};
// Required 的实现
type Required<T> = {
[P in keyof T]-?: T[P];
};
// Pick 的实现
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
6. 错误提示
interface 的错误提示通常更友好,会显示接口名称:
interface UserInterface {
name: string;
age: number;
}
type UserType = {
name: string;
age: number;
};
// 错误提示中,interface 显示 "UserInterface"
// type 可能显示展开的类型结构
对比总结
| 特性 | interface | type |
|---|---|---|
| 定义对象类型 | ✅ | ✅ |
| 定义函数类型 | ✅ | ✅ |
| 继承/扩展 | ✅ extends | ✅ & |
| 实现类 | ✅ | ✅ |
| 声明合并 | ✅ | ❌ |
| 基本类型别名 | ❌ | ✅ |
| 联合类型 | ❌ | ✅ |
| 元组类型 | ❌ | ✅ |
| 映射类型 | ❌ | ✅ |
| 条件类型 | ❌ | ✅ |
最佳实践
使用 interface 的场景
// 1. 定义对象结构(尤其是公共 API)
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// 2. 需要声明合并
interface Window {
myCustomProperty: string;
}
// 3. 定义类实现的契约
interface Repository<T> {
find(id: string): Promise<T>;
save(entity: T): Promise<void>;
delete(id: string): Promise<void>;
}
class UserRepository implements Repository<User> {
// ...
}
// 4. 面向对象编程风格
interface Animal {
name: string;
speak(): void;
}
interface Dog extends Animal {
breed: string;
fetch(): void;
}
使用 type 的场景
// 1. 联合类型
type Status = 'pending' | 'active' | 'inactive';
type ID = string | number;
// 2. 元组类型
type Coordinate = [number, number];
type RGB = [red: number, green: number, blue: number];
// 3. 复杂类型组合
type UserWithPermissions = User & {
permissions: string[];
};
// 4. 映射类型和条件类型
type Nullable<T> = T | null;
type ReadonlyDeep<T> = {
readonly [P in keyof T]: ReadonlyDeep<T[P]>;
};
// 5. 从值推断类型
const config = {
api: 'https://api.example.com',
timeout: 5000
} as const;
type Config = typeof config;
// 6. 提取类型
type ArrayElement<T> = T extends (infer E)[] ? E : never;
团队规范建议
// 推荐的团队规范
// 1. 优先使用 interface 定义对象类型
interface User {
id: string;
name: string;
email: string;
}
// 2. 使用 type 定义联合类型、工具类型
type UserRole = 'admin' | 'user' | 'guest';
type PartialUser = Partial<User>;
// 3. 保持一致性,项目内统一风格
// 如果项目已经大量使用某种方式,保持一致
// 4. 复杂类型组合使用 type
type CreateUserInput = Pick<User, 'name' | 'email'> & {
password: string;
};
常见面试问题
Q1: interface 和 type 可以互相扩展吗?
答案:
可以,它们可以互相扩展:
// type 扩展 interface
interface Animal {
name: string;
}
type Dog = Animal & {
breed: string;
};
// interface 扩展 type
type Person = {
name: string;
age: number;
};
interface Employee extends Person {
employeeId: number;
department: string;
}
// interface 扩展多个 type
type HasName = { name: string };
type HasAge = { age: number };
interface User extends HasName, HasAge {
email: string;
}
Q2: 为什么要有两种方式?
答案:
历史原因和设计哲学:
- interface 来自面向对象编程,用于定义对象的形状和类的契约
- type 更灵活,支持更复杂的类型操作
TypeScript 团队的建议:
- 如果两者都可以用,优先使用
interface - 需要联合类型、元组、映射类型时使用
type - 需要声明合并时必须用
interface
Q3: 性能上有区别吗?
答案:
通常没有运行时性能区别,因为类型信息在编译后会被移除。
但在编译时:
interface在某些情况下可能稍快,因为编译器对其有特殊优化- 复杂的递归
type可能导致编译变慢 - 实际项目中差异可以忽略
// 可能导致编译变慢的复杂递归类型
type DeepPartial<T> = T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T;
Q4: 什么情况下必须用 interface?
答案:
需要声明合并时必须用 interface:
// 扩展全局类型
declare global {
interface Window {
gtag: (...args: any[]) => void;
}
}
// 扩展第三方库类型
declare module 'express' {
interface Request {
user?: { id: string; name: string };
}
}
// 模块增强
declare module 'vue' {
interface ComponentCustomProperties {
$myPlugin: MyPluginType;
}
}
Q5: 什么情况下必须用 type?
答案:
// 1. 联合类型
type Status = 'success' | 'error' | 'loading';
// 2. 元组
type Pair<T> = [T, T];
// 3. 映射类型
type Readonly<T> = { readonly [K in keyof T]: T[K] };
// 4. 条件类型
type Unwrap<T> = T extends Promise<infer U> ? U : T;
// 5. 从值提取类型
const colors = ['red', 'green', 'blue'] as const;
type Color = typeof colors[number]; // 'red' | 'green' | 'blue'
// 6. 模板字面量类型
type EventName = `on${Capitalize<'click' | 'focus' | 'blur'>}`;
// 'onClick' | 'onFocus' | 'onBlur'
Q6: 什么时候用 type 什么时候用 interface?项目统一规范建议
答案:
核心原则:能用 interface 就用 interface,需要高级类型能力时用 type。以下是详细的决策指南:
// ✅ 用 interface:定义对象形状、公共 API 契约、类实现
interface UserProfile {
id: string;
name: string;
avatar: string;
}
interface UserService {
getUser(id: string): Promise<UserProfile>;
updateUser(id: string, data: Partial<UserProfile>): Promise<void>;
}
class UserServiceImpl implements UserService {
async getUser(id: string): Promise<UserProfile> {
// ...
return { id, name: 'Alice', avatar: '' };
}
async updateUser(id: string, data: Partial<UserProfile>): Promise<void> {
// ...
}
}
// ✅ 用 type:联合类型、元组、映射类型、条件类型、从值推断类型
type Status = 'active' | 'inactive' | 'banned';
type Coordinate = [x: number, y: number];
type Nullable<T> = T | null;
type UserKeys = keyof UserProfile;
- ESLint 规则统一:使用
@typescript-eslint/consistent-type-definitions强制统一风格 - Props 类型:React 组件的 Props 推荐用
interface(可扩展,错误提示更友好) - 函数参数:简单对象参数用
interface,复杂组合用type - 导出类型:公共 API 优先
interface(方便消费方通过声明合并扩展) - 内部类型:项目内部的工具类型、联合类型统一用
type
// 强制项目内统一使用 interface 定义对象类型
module.exports = {
rules: {
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
},
};
| 场景 | 推荐 | 原因 |
|---|---|---|
| 组件 Props/State | interface | 可扩展、报错清晰 |
| API 响应类型 | interface | 方便声明合并扩展 |
| 联合类型/字面量类型 | type | interface 不支持 |
| 工具类型/条件类型 | type | interface 不支持 |
| 函数类型 | type | 语法更直观 |
| 第三方库扩展 | interface | 声明合并 |
Q7: interface 的声明合并(Declaration Merging)有什么实际用途?
答案:
声明合并是 interface 独有的能力,允许同名 interface 自动合并属性。这在以下场景中非常实用:
1. 扩展第三方库的类型定义
// 给 Express 的 Request 添加自定义属性
declare module 'express' {
interface Request {
user?: {
id: string;
role: 'admin' | 'user';
};
requestId: string;
}
}
// 使用时自动获得类型提示
import { Request, Response } from 'express';
function authMiddleware(req: Request, res: Response, next: Function): void {
req.user = { id: '123', role: 'admin' }; // 有类型提示,不报错
req.requestId = crypto.randomUUID();
next();
}
2. 扩展全局对象
// 扩展 Window 对象
declare global {
interface Window {
__APP_CONFIG__: {
apiUrl: string;
env: 'dev' | 'staging' | 'prod';
};
gtag: (...args: unknown[]) => void;
}
}
// 使用
const config = window.__APP_CONFIG__;
console.log(config.apiUrl); // 有类型提示
3. 扩展 Vue/React 的类型
// Vue 全局属性扩展
declare module 'vue' {
interface ComponentCustomProperties {
$http: typeof axios;
$toast: (message: string) => void;
}
}
// 使用时 this.$http、this.$toast 有类型提示
4. 插件系统的类型扩展
// 基础接口
interface PluginHooks {
onInit(): void;
}
// 插件 A 扩展
interface PluginHooks {
onAuth(token: string): void;
}
// 插件 B 扩展
interface PluginHooks {
onAnalytics(event: string, data: Record<string, unknown>): void;
}
// 最终合并结果:同时拥有 onInit、onAuth、onAnalytics
const hooks: PluginHooks = {
onInit() {},
onAuth(token) {},
onAnalytics(event, data) {},
};
声明合并要求同名属性的类型必须一致,否则会报错:
interface User {
name: string;
}
interface User {
// name: number; // 错误:后续声明的 name 类型必须与前面一致
age: number; // OK:新增属性没问题
}
Q8: extends 和交叉类型 & 在继承/组合时的区别
答案:
虽然 extends(interface 继承)和 &(type 交叉)都能实现类型组合,但它们在冲突处理、性能、错误提示上有重要区别。
1. 类型冲突处理不同
// extends:属性类型冲突直接报错(编译时阻止)
interface Base {
id: string;
value: number;
}
// interface Child extends Base { // 错误!'value' 类型不兼容
// value: string;
// }
// &:属性类型冲突不报错,但结果是 never(运行时出问题)
type BaseType = {
id: string;
value: number;
};
type ChildType = BaseType & {
value: string; // 不报错!
};
// ChildType['value'] 的类型是 number & string = never
// 意味着没有值能满足这个类型,实际上无法使用
extends 在冲突时立即报错,帮你发现问题;& 在冲突时静默产生 never,可能导致难以排查的 bug。
2. 错误提示差异
// extends:报错信息明确指出冲突属性
interface A {
x: number;
y: string;
}
// interface B extends A { x: string }
// 错误提示:Interface 'B' incorrectly extends interface 'A'.
// Types of property 'x' are incompatible.
// Type 'string' is not assignable to type 'number'.
// &:不会报错,但使用时可能得到令人困惑的 never
type C = A & { x: string };
// 试图赋值时才发现问题
// const c: C = { x: ???, y: 'hello' }; // x 需要同时是 number 和 string
3. 编译性能差异
// interface extends:编译器有专门优化,缓存扁平结构
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// 编译器内部直接生成扁平的属性映射,查找效率高
// type &:每次使用都需要计算合并结果
type AnimalType = { name: string };
type DogType = AnimalType & { breed: string };
// 编译器每次遇到 DogType 都要重新计算交叉类型的结果
// 在大型项目中可能导致编译变慢
4. 支持的组合方式
// extends 只能继承对象类型
interface Named {
name: string;
}
interface Aged {
age: number;
}
interface Person extends Named, Aged {
email: string;
}
// & 可以组合任意类型(更灵活)
type StringOrNumber = string | number;
type WithId = StringOrNumber & { id: string }; // ❌ 不合理但语法允许
// 实际中 & 常用于组合对象类型
type PersonType = Named & Aged & { email: string };
// & 还能配合联合类型使用
type AdminUser = User & { role: 'admin'; permissions: string[] };
type GuestUser = User & { role: 'guest' };
type AnyUser = AdminUser | GuestUser;
对比总结
| 特性 | extends | & |
|---|---|---|
| 冲突检测 | 编译时报错 | 静默产生 never |
| 错误提示 | 清晰明确 | 不直观 |
| 编译性能 | 更优(有缓存) | 每次重新计算 |
| 灵活性 | 只能组合对象类型 | 可组合任意类型 |
| 适用场景 | 稳定的继承关系 | 灵活的类型组合 |
- 明确的继承关系用
interface extends(更安全、更快) - 临时的类型组合用
&(更灵活) - 如果需要对联合类型、条件类型等做组合,只能用
&