接口(Interface)
- 优势:
- 声明合并:接口可以定义多次,编译器会将它们合并成一个接口。这在大型项目中,不同模块需要对同一个对象结构进行补充或扩展时非常有用。
interface User {
name: string;
}
interface User {
age: number;
}
let user: User = {name: 'John', age: 30};
- 自动添加索引签名:当需要描述一个具有某些已知属性,同时可能还有其他任意属性的对象时,接口可以方便地添加索引签名。
interface Options {
color: string;
[key: string]: any;
}
let opts: Options = {color: 'red', size: 'large'};
- 实现约束:类可以实现接口,确保类具有接口定义的结构。这在面向对象编程中,对类的结构进行规范很有帮助。
interface Drawable {
draw(): void;
}
class Circle implements Drawable {
draw() {
console.log('Drawing a circle');
}
}
- 劣势:
- 不支持基本类型别名:接口只能描述对象类型,不能用于给基本类型(如
string
、number
)创建别名。
- 条件类型和映射类型有限:相比类型别名,接口在处理复杂的条件类型和映射类型时不够灵活。例如,无法直接在接口中使用条件类型来创建动态的属性类型。
类型别名(Type Alias)
- 优势:
- 更通用:可以为任何类型创建别名,包括基本类型、联合类型、元组类型等。
type MyString = string;
type MyNumberOrString = number | string;
type MyTuple = [string, number];
- 灵活的条件类型和映射类型:在处理复杂类型变换时,类型别名更具优势。可以轻松使用条件类型、映射类型来生成新类型。
type Optional<T> = {
[P in keyof T]?: T[P];
};
type User = {name: string; age: number};
type OptionalUser = Optional<User>;
- 类型保护:在类型别名中使用
typeof
、instanceof
等类型保护机制更方便,有助于编写更健壮的代码。
type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Animal = Fish | Bird;
function isFish(animal: Animal): animal is Fish {
return (animal as Fish).swim!== undefined;
}
- 劣势:
- 没有声明合并:类型别名不能像接口那样定义多次并合并,若要扩展,通常需要重新定义一个新的类型别名。
选择建议
- 对象结构定义且可能需要扩展:如果是定义对象结构,并且预计在不同模块中会对其进行扩展,优先使用接口。例如,定义一个用户相关的对象,不同模块可能会添加不同的属性,接口的声明合并特性可以很好地满足需求。
// 用户基础信息
interface UserBase {
name: string;
}
// 用户详细信息模块
interface UserBase {
age: number;
}
- 复杂类型变换:当涉及到复杂的条件类型、映射类型操作,或者需要对基本类型、联合类型创建别名时,使用类型别名。比如,对一个对象的属性进行可选化处理,类型别名的映射类型可以简洁实现。
type Optional<T> = {
[P in keyof T]?: T[P];
};
type Data = { id: number; value: string };
type OptionalData = Optional<Data>;
- 类的实现约束:如果要规范类的结构,让类实现某个类型定义,使用接口。例如,定义一个可序列化的接口,让类实现该接口以确保具有
serialize
方法。
interface Serializable {
serialize(): string;
}
class MyClass implements Serializable {
serialize() {
return 'Serialized data';
}
}