类型和接口之间的区别
摘抄自《TypeScript编程》Boris Cherny著 安道译
1. 类型和接口的共同点
与类型别名相似,接口是一种命名类型的方式,这样就不用在行内定义了。类型别名和接口算是同一概念的两种语法(就像函数表达式和函数声明之间的关系),不过二者之间还是有一些细微差别。先看二者的共同点。以下述类型别名为例:
在使用Sushi类型别名的地方都能使用Sushi接口。两个声明都定义结构,而且二者可以相互赋值(其实,二者完全一样)。
// 类型 type Sushi = { calories: number, salty: boolean, tasty: boolean } // 接口 interface Sushi { calories: number, salty: boolean, tasty: boolean }
把类型组合在一起时,更为有趣。下面为Sushi之外的另一个食物建模:
type Sushi = { calories: number, salty: boolean, tasty: boolean } type Cake = { calories: number, sweet: boolean, tasty: boolean } // 类型组合 type Food = { calories: number, tasty: boolean } type Sushi = Food & { salty: boolean } type Cake = Food & { sweet: boolean } // 接口定义 interface Food { calories: number, tasty: boolean } interface Sushi extends Food { salty: boolean } interface Cake extends Food { sweet: boolean }
2. 类型和接口的细微差别
1)类型别名更为通用,右边可以是任何类型,包括类型表达式(类型,外加&或|等类型运算符);而在接口声明中,右边必须为结构。例如,下述类型别名不能使用接口重写:
type A = number; type B = A | string;
2)扩展接口时,TypeScript将检查扩展的接口是否可赋值给被扩展的接口。而使用交集类型时则不会出现这种问题。如果把下例中的接口换成类型别名,把extends换成交集运算符(&),TypeScript将尽自己所能,把扩展和被扩展的类型组合在一起,最终的结果是重载bad的签名,而不会抛出编译时错误。建模对象类型的继承时,TypeScript对接口所做的可赋值性检查是捕获错误的有力工具。
interface A { good(x: number): string, bad(x: number): string } /** * Interface 'B' incorrectly extends interface 'A'. Types of property 'bad' are incompatible. Type '(x: string) => string' is not assignable to type '(x: number) => string'. Types of parameters 'x' and 'x' are incompatible. Type 'number' is not assignable to type 'string'.ts(2430) */ interface B extends A { good(x: string | number): string, bad(x: string): string }
3)第三个区别是,同一作用域中的多个同名接口将自动合并;同一作用域中的多个同名类型别名将导致编译时错误。这个特性称为声明合并。