本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!
1. 对象(Objects) / 方法(Functions)
二者均可被用来声明对象
和方法
的签名,但语法不同。
接口
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
复制代码
类型别名
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
复制代码
2. 其他类型
与接口
不同,类型别名
可以被用于其他类型,如基本类型、联合类型和元组。
// 基本类型
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// 联合类型
type PartialPoint = PartialPointX | PartialPointY;
// 元组
type Data = [number, string];
复制代码
3. 扩展(Extend)
二者均可扩展,但语法不同。另外,请注意接口
和类型别名
并不是互斥的。接口
可以扩展类型别名
,反之亦然。
接口扩展接口
interface PartialPointX { x: number; }
interface Point extends PartialPointX { y: number; }
复制代码
类型别名扩展类型别名
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
复制代码
接口扩展类型别名
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
复制代码
类型别名扩展接口
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
复制代码
4. 实现(Implements)
类
可以以完全相同的方式实现接口
或类型别名
。 但是请注意,类
和接口
被视为静态蓝图。 因此,他们不能实现或扩展被定义为联合类型
的类型别名
。
interface Point {
x: number;
y: number;
}
class SomePoint implements Point {
x = 1;
y = 2;
}
type Point2 = {
x: number;
y: number;
};
class SomePoint2 implements Point2 {
x = 1;
y = 2;
}
type PartialPoint = { x: number; } | { y: number; };
// FIXME: 不可以实现一个联合类型
class SomePartialPoint implements PartialPoint {
x = 1;
y = 2;
}
复制代码
5. 声明合并
与类型别名
不同,接口
可以被定义多次,并将被视为一个接口
(合并了所有声明的成员)。
// 这里的两个定义将合并为:
// interface Point { x: number; y: number; }
interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };
复制代码
什么时候使用接口,什么时候使用类型别名?
参考资料
Abstract data type
Algebraic data type
Writing Easy-to-Compile Code
抽象数据类型
An abstract data type is a type with associated operations, but whose representation is hidden.
抽象数据类型是具有相关行为的类型,但其内部实现是隐藏的。
接口
更适合用来定义抽象数据类型
,描述中所说的行为将被定义为接口
中的方法,此时只专注于定义类型的行为,而忽略其内部数据。
当然 TypeScript 的接口中是可以定义数据的,但是由于在 C# / Java 这样的完全面向对象的语言中,接口
本身就被限制为只能够定义方法,而不可定义数据,所以此时使用接口
是一件更自然的事情。
如下使用接口定义栈。
interface Stack<T> {
isEmpty(): boolean
push(value: T): number
pop(): T
top(): T
}
复制代码
代数数据类型
This is a type where we specify the shape of each of the elements. Wikipedia has a thorough discussion. “Algebraic” refers to the property that an Algebraic Data Type is created by “algebraic” operations. The “algebra” here is “sums” and “products”:
- “sum” is alternation (A | B, meaning A or B but not both)
- “product” is combination (A B, meaning A and B together)
在这种类型中我们指定每个元素的形状。“代数”是一个性质,指代数数据类型由“代数”操作创建。这里的“代数”是“和(sum)”和“乘积(product)”:
- “和”代表备选(A | B,意味着 A 或者 B 但不是全部)
- “乘积”代表组合(A B,意味着 A 和 B 一起)
类型别名就更适合定义代数数据类型
,显然 TypeScript 中的“和”运算符是|
,“乘积(product)”运算符是&
。
5
/ \
3 7
/ \
1 4
复制代码
下面使用类型别名
描述上面给出的二叉树结构。
// 使用对象定义二叉树
type Stree<T> = undefined | {
data: T,
left: Stree<T>,
right: Stree<T>
}
const stree: Stree<number> = {
data: 5,
left: {
data: 3,
left: {
data: 1,
left: undefined,
right: undefined
},
right: {
data: 4,
left: undefined,
right: undefined
}
},
right: {
data: 7,
left: undefined,
right: undefined
}
}
// 使用元组定义二叉树
type Stree<T> = undefined | [Stree<T>, T, Stree<T>]
const stree: Stree<number> = [
[
[
undefined,
1,
undefined
],
3,
[
undefined,
4,
undefined
]
],
5,
[
undefined,
7,
undefined
]
]
复制代码
编译性能
大多数情况下,用于声明对象类型的类型别名与接口的行为非常相似。
interface Foo { prop: string }
type Bar = { prop: string };
复制代码
但是,当你需要组合两个或多个类型时,可以使用接口进行扩展,也可以使用类型别名进行交叉,此时开始存在差异。
接口创建一个对象类型,并检测属性是否冲突,解决这些冲突通常是很重要的。交叉只是递归地合并属性,在某些情况下将产生 never
。接口总是展示的更好,而交叉的类型别名作为其它交叉的类型别名的一部分时不会被展示。具有最后一个值得注意的区别是,在交叉类型进行检查时,在检查交叉最终产生的类型之前,先对每个组成部分进行检查。
因此,建议使用接口/扩展来组合类型,而不是使用交叉类型。
type Foo = Bar & Baz & {
someProp: string;
}
interface Foo extends Bar, Baz {
someProp: string;
}
复制代码