TypeScript 中的别名与结构化行为
本课程展示了 TypeScript 的一个基本特性,即依赖对象的结构。
TypeScript 的结构化特性
TypeScript 是一种结构类型语言,而不是名义类型语言。这意味着 TypeScript 在判断对象是否相似时,比较的是对象的结构,而非名称。
以下代码中的函数接收一个匿名类型参数,该类型包含一个 name 属性。示例展示了三种调用函数的方式:
- 使用接口(interface)
- 使用类型别名(type)
- 使用匿名对象
这三种情况都能正常工作,因为它们都具有完全匹配的结构。
在下面的代码中,MyTypeA 在第 2 行定义了一个对象的结构,包含一个名为 name 的成员。第 4 行的类型与之相同,也等同于第 6 行的匿名参数。
第 10-11 行定义了两个类型,第 12-13 行调用函数,即使传入的不是匿名参数。这仍然可以运行,因为它们具有相同的结构。TypeScript 是结构类型语言,而非名义类型语言。因此,第 14 行的函数调用也是合法的。
1interface MyTypeA { 2 name: string; // 与第 4 行和第 6 行的属性名类型一致 3} 4type MyTypeB = { name: string }; // 注意该属性名与第 6 行的匿名类型相同 5 6function ouputName(obj: { name: string }): void { // 第一个参数为匿名类型 7 console.log(obj.name); 8} 9 10let typeA: MyTypeA = { name: "A" }; // TypeA 11let typeB: MyTypeB = { name: "B" }; // TypeB 12ouputName(typeA); 13ouputName(typeB); 14ouputName({ name: "C" }); // 也是匿名对象
添加额外字段
重要的一点是:类型不需要与目标类型完全一致。只要类型在结构上完全兼容即可,甚至可以包含额外的字段。以下示例中,InterfaceB 在第 7 行多了一个 id 字段。尽管第 13 行的调用并不需要这个字段,但由于 TypeScript 的结构化特性,该调用依然能成功编译。
1interface InterfaceA { 2 name: string; 3} 4 5interface InterfaceB { 6 name: string; 7 id: number; 8} 9 10let intA: InterfaceA = { name: "A" }; 11let intB: InterfaceB = { name: "B", id: 2 }; 12 13function outputName(obj: { name: string }): void { 14 console.log(obj.name); 15} 16 17outputName(intA); 18outputName(intB);
上述代码的另一种写法如下:
1interface InterfaceA { 2 name: string; 3} 4 5interface InterfaceB { 6 name: string; 7 id: number; 8} 9 10let intA: InterfaceA = { name: "A" }; 11let intB: InterfaceB = { name: "B", id: 2 }; 12 13function outputInterfaceA(obj: InterfaceA): void { // 非匿名类型 14 console.log(obj.name); 15} 16 17outputInterfaceA(intA); 18outputInterfaceA(intB); // InterfaceB 可以替代 InterfaceA
尽管第 13 行要求传入 InterfaceA,而 InterfaceB 并未显式实现或继承 InterfaceA,但代码仍能编译通过,因为它包含了所有必需的结构字段(name: string)。
优化代码
为了减少冗余,更好的做法是让 InterfaceB 继承 InterfaceA。
1interface InterfaceA { 2 name: string; 3} 4 5interface InterfaceB extends InterfaceA { 6 id: number; 7} 8 9let intA: InterfaceA = { name: "A" }; 10let intB: InterfaceB = { name: "B", id: 2 }; 11 12function outputInterfaceA(obj: InterfaceA): void { 13 console.log(obj.name); 14} 15 16outputInterfaceA(intA); 17outputInterfaceA(intB);
本节课我们了解到,对 TypeScript 而言,关键在于满足结构契约,而非名义契约。这意味着我们可以根据需要为不同的结构创建别名或快捷方式。在接下来的课程中,我们将学习不同类型的别名。