关于 type-vs-interface
这项 TypeScript 技能说明如何在声明和组合类型时选择 `type` 或 `interface`,帮助开发者处理对象结构、联合类型、元组及可扩展 API。
主要功能
- 01按类型形态和用途选择
- 02用 interface 定义可扩展对象
- 03用别名定义联合类型和元组
- 04理解声明合并
- 05通过扩展或交叉组合类型
适用场景
- 01定义公共 API 的对象结构
- 02建模联合类型和元组数据
- 03为库类型选择声明方式
- 04保持代码库中的类型选择一致
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill type-vs-interface安装后在 Claude Code 中输入/type-vs-interface确认已加载。
详细介绍
了解 type 和 interface 的区别
概述
对象类型使用 interface,其他情况使用 type。
type 和 interface 都可以定义对象类型,但它们的功能有所不同。了解这些区别有助于你选择合适的工具并编写风格一致的代码。
何时使用此技能
- 定义任何命名类型
- 在类型别名和接口之间做选择
- 扩展或组合类型
- 处理声明文件
- 理解库的类型定义
铁律
Use INTERFACE for object types that could be extended.
Use TYPE for unions, tuples, mapped types, and complex compositions.
请记住:
- 接口支持声明合并
- 类型别名可以表示更复杂的类型
- 两者都可以扩展和实现
- 在代码库中保持一致最重要
快速决策指南
| 场景 | 使用 |
|---|---|
| 对象类型(API 响应、props) | interface |
| 联合类型 | type |
| 元组类型 | type |
| 函数类型 | type |
| 映射类型 | type |
| 原始类型别名 | type |
| 旨在供扩展的库类型 | interface |
相似之处
两者都能定义对象形状
type TState = {
name: string;
capital: string;
};
interface IState {
name: string;
capital: string;
}
// These are interchangeable for most purposes
两者都支持泛型
type TBox<T> = { value: T };
interface IBox<T> { value: T }
两者都可以扩展
// Interface extending type
interface IStateWithPop extends TState {
population: number;
}
// Type extending interface
type TStateWithPop = IState & { population: number };
两者都可以实现
class StateImpl implements IState {
name = '';
capital = '';
}
区别
1. 联合类型(仅限 type)
type StringOrNumber = string | number; // Only with type
type Status = 'pending' | 'fulfilled' | 'rejected';
// Can't do this with interface
2. 元组类型(仅限 type)
type Pair = [number, number];
type NamedNums = [string, ...number[]];
// Interface syntax is awkward
interface IPair {
0: number;
1: number;
length: 2;
}
3. 声明合并(仅限 interface)
interface User {
name: string;
}
interface User {
email: string;
}
// Now User has both name and email
const user: User = { name: 'Alice', email: '[email protected]' };
TypeScript 正是通过这种方式在不同 ES 版本间扩展标准库类型。
4. 更好的错误消息(interface)
interface Person {
name: string;
age: string; // Note: string
}
// Type intersection silently creates unusable type
type TPerson = Person & { age: number }; // No error, but age is never
// Interface extension gives helpful error
interface IPerson extends Person {
age: number;
// ~~~ Types of property 'age' are incompatible
}
5. 类型别名内联
TypeScript 可能会在错误消息和 .d.ts 文件中内联类型别名:
// In generated .d.ts, type aliases may be expanded
type Point = { x: number; y: number };
export function getOrigin(): Point { ... }
// May become:
export function getOrigin(): { x: number; y: number };
// Interfaces are preserved by name
interface IPoint { x: number; y: number }
export function getOrigin(): IPoint { ... }
// Stays as:
export function getOrigin(): IPoint;
何时使用 interface
定义公共 API
// Users might want to extend this
export interface RequestOptions {
url: string;
method?: string;
headers?: Record<string, string>;
}
// Declaration merging allows extension
declare module 'my-lib' {
interface RequestOptions {
timeout?: number; // User adds this
}
}
一般对象类型
interface User {
id: string;
name: string;
email: string;
}
interface Post {
id: string;
title: string;
author: User;
}
何时使用 type
联合类型
type Result<T> = { ok: true; value: T } | { ok: false; error: Error };
type Status = 'loading' | 'success' | 'error';
元组和数组
type Point = [x: number, y: number];
type RGB = [number, number, number];
函数类型
type Handler = (event: Event) => void;
type AsyncFn<T> = () => Promise<T>;
映射类型和条件类型
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type NonNullable<T> = T extends null | undefined ? never : T;
计算类型
type Keys = keyof User; // 'id' | 'name' | 'email'
type UserValues = User[keyof User]; // string
扩展模式
接口扩展接口
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
类型交叉类型
type Animal = { name: string };
type Dog = Animal & { breed: string };
混合使用(两种方式都可行)
// Interface extends type
interface Dog extends Animal { breed: string; }
// Type extends interface
type Cat = Animal & { meows: boolean };
抵御压力原则
1. “选一个,然后坚持使用”
压力:“保持一致比选择什么更重要”
**回应:**没错,但要清楚自己为什么这样选。
**行动:**对象默认使用 interface,其他情况使用 type。
2. “我需要接口的联合类型”
压力:“我的接口应该构成一个联合类型”
**回应:**你可以用类型别名对接口进行联合。
行动:type Either = InterfaceA | InterfaceB;
警示信号——停止并重新考虑
- 使用
interface定义元组类型 - 需要声明合并时却使用
type - 在代码库中对相似类型的用法不一致
常见的自我辩解(均不成立)
| 借口 | 实际情况 |
|---|---|
| “它们完全一样” | 不,声明合并和联合类型方面存在差异 |
| “Type 总是更好” | Interface 能提供更好的错误消息,并支持声明合并 |
| “Interface 总是更好” | 它无法表示联合类型、元组或映射类型 |
快速参考
// USE INTERFACE FOR:
interface User { name: string; } // Object types
interface Config extends Base { } // Extendable types
// USE TYPE FOR:
type ID = string | number; // Unions
type Point = [number, number]; // Tuples
type Handler = () => void; // Functions
type Keys = keyof User; // Computed types
type Mapped = { [K in Keys]: boolean }; // Mapped types
总结
对象类型使用 interface,其他情况使用 type。
接口支持声明合并,产生的错误消息也更好。联合类型、元组和复杂的类型操作则需要类型别名。在代码库中保持一致。
参考资料
根据 Dan Vanderkam 所著的《Effective TypeScript》第 13 条:“了解 type 和 interface 的区别”。
评论
支持 Markdown