TypeScript 的结构类型
TypeScript 作为 JavaScript 的超集,为 JavaScript 引入了静态类型,允许开发者捕获潜在错误并提高代码质量。TypeScript 的关键特性之一是其对结构类型的支持。虽然其他静态类型语言通常依赖于名义类型,但 TypeScript 采用了结构类型,这提供了一种更灵活和直观的方法来进行类型检查。
在本教程中,我们将探讨 TypeScript 中结构类型的概念及其优势,并提供相关的示例来说明其用法。
理解结构类型
结构类型是一种类型系统,它关注对象的形状或结构,而不是其特定的名称或类。换句话说,如果两个对象具有相同的属性和方法集,则它们被认为是兼容的,而不管它们的显式声明或继承层次结构如何。这种方法提高了代码的可重用性,并鼓励使用鸭子类型,其中对象的适用性取决于其行为,而不是其类或类型。
什么是鸭子类型?
鸭子类型是编程中的一种概念,它关注对象的行为而不是其特定的类型或类。“鸭子类型”一词源于一句谚语:“如果它看起来像鸭子,叫起来也像鸭子,那么它很可能就是鸭子。”换句话说,鸭子类型根据对象是否支持所需的方法和属性来确定其适用性,而不是依赖于显式类型声明。
结构类型的优势
示例 1
鸭子类型和多态性 - 结构类型支持鸭子类型,允许开发者编写更灵活和可重用的代码。多态性作为面向对象编程中的一个基本原则,更容易实现,因为具有匹配结构的对象可以互换使用。
在下面的示例中,printItem 函数接受类型为 Printable 的对象。任何具有 print 方法的对象都可以传递给此函数,而不管其显式声明或类如何。
interface Printable { print(): void; } class Doc implements Printable { print() { console.log("Printing document..."); } } class DocExtended implements Printable { print(): void { console.log("Printing from extended documented..."); } wow(): void { console.log("This works!!"); } } function printItem(item: Printable) { item.print(); } const doc = new Doc(); printItem(doc); const docExtended = new DocExtended(); printItem(docExtended);
编译后,它将生成以下 JavaScript 代码:
var Doc = /** @class */ (function () { function Doc() { } Doc.prototype.print = function () { console.log("Printing document..."); }; return Doc; }()); var DocExtended = /** @class */ (function () { function DocExtended() { } DocExtended.prototype.print = function () { console.log("Printing from extended documented..."); }; DocExtended.prototype.wow = function () { console.log("This works!!"); }; return DocExtended; }()); function printItem(item) { item.print(); } var doc = new Doc(); printItem(doc); var docExtended = new DocExtended(); printItem(docExtended);
输出
上面的代码将产生以下输出:
Printing document... Printing from extended documented...
示例 2
结构子类型 - 结构类型支持隐式接口,也称为结构子类型。TypeScript 允许对象隐式地符合预期的结构,而不是显式定义接口。这通过减少对显式接口声明的需求来简化代码维护。
在下面的示例中,logMessage 函数期望一个具有类型为字符串的 text 属性的对象。TypeScript 根据对象的结构推断类型,允许我们直接传递对象字面量而无需定义其类型。
function logMessage(message: { text: string }) { console.log(message.text); } const message = { text: "Hello, world!" }; logMessage(message);
编译后,它将生成以下 JavaScript 代码:
function logMessage(message) { console.log(message.text); } var message = { text: "Hello, world!" }; logMessage(message);
输出
上面的代码将产生以下输出:
Hello, world!
示例 3
灵活的类型兼容性 - 结构类型在类型兼容性方面提供了更大的灵活性。具有相同结构的两个对象可以相互赋值,即使它们的显式类型不同。这促进了不同应用程序部分之间的互操作性和代码重用。
在上面的示例中,Circle 对象被赋值给 Shape 类型的变量,因为 Circle 的结构与 Shape 接口中定义的属性匹配。一旦 Circle 对象被定义为 Shape 类型,Circle 类中额外的方法,例如 sayHello,或属性将不可访问。因此,shape 对象无法访问 sayHello 函数。
interface Shape { color: string; display: () =&g; void; } class Circle { color: string; radius: number; constructor(color: string, radius: number) { this.color = color; this.radius = radius; } display(): void { console.log(`The value of color is: ${this.color}`); console.log(`The value of radius is: ${this.radius}`); } sayHello() { console.log( "Hey there! I am a circle but still compatible with Shape interface..." ); } } const shape: Shape = new Circle("red", 5); shape.display();
编译后,它将生成以下 JavaScript 代码:
var Circle = /** @class */ (function () { function Circle(color, radius) { this.color = color; this.radius = radius; } Circle.prototype.display = function () { console.log("The value of color is: ".concat(this.color)); console.log("The value of radius is: ".concat(this.radius)); }; Circle.prototype.sayHello = function () { console.log("Hey there! I am a circle but still compatible with Shape interface..."); }; return Circle; }()); var shape = new Circle("red", 5); shape.display();
输出
上面的代码将产生以下输出:
The value of color is: red The value of radius is: 5
示例 4
开放可扩展系统 - 结构类型允许创建开放和可扩展的系统,其中可以无缝地添加和集成新类型。由于兼容性基于对象的结构,因此向现有对象添加新属性或方法不会破坏与代码库其他部分的兼容性。这使得更容易演化和扩展代码,而不会在整个系统中导致级联更改。
在这个例子中,即使 circle 对象具有额外的 radius 属性,只要它具有 color 属性,它仍然与 Shape 接口兼容。
interface Shape { color: string; } function printShapeColor(shape: Shape) { console.log(shape.color); } const circle = { color: "blue", radius: 5 }; printShapeColor(circle); // Prints "blue"
编译后,它将生成以下 JavaScript 代码:
function printShapeColor(shape) { console.log(shape.color); } var circle = { color: "blue", radius: 5 }; printShapeColor(circle); // Prints "blue"
输出
上面的代码将产生以下输出:
blue
示例 5
隐式转换和互操作性 - 结构类型支持具有兼容结构的类型之间的隐式转换。这使得更容易处理可能不显式匹配预期类型的库或来自外部源的代码。TypeScript 可以自动推断结构兼容性并执行必要的转换,而无需显式类型注释或转换。
在这个例子中,customer 对象具有额外的 address 属性,但是 TypeScript 仍然可以推断它与 Person 接口的兼容性,允许它在不出现错误的情况下传递给 greet 函数。
interface Person { name: string; age: number; } function greet(person: Person) { console.log(`Hello, ${person.name}! You are ${person.age} years old.`); } const customer = { name: "Alice", age: 30, address: "123 Street" }; greet(customer); // Prints "Hello, Alice! You are 30 years old."
编译后,它将生成以下 JavaScript 代码:
function greet(person) { console.log("Hello, ".concat(person.name, "! You are ").concat(person.age, " years old.")); } var customer = { name: "Alice", age: 30, address: "123 Street" }; greet(customer); // Prints "Hello, Alice! You are 30 years old."
输出
上面的代码将产生以下输出:
Hello, Alice! You are 30 years old.
示例 6
轻松集成现有 JavaScript 代码 - TypeScript 的结构类型允许轻松集成现有的 JavaScript 代码库。由于 JavaScript 是动态类型的,并且通常依赖于鸭子类型,因此结构类型与 JavaScript 的运行时行为非常吻合。开发者可以逐步将 TypeScript 引入他们的 JavaScript 项目,而无需立即为所有对象定义显式接口。
在这个 JavaScript 代码片段中,没有显式的类型或接口定义。TypeScript 可以推断 greeting 对象的结构,并在与 printMessage 函数交互时确保类型安全。
// JavaScript code function printMessage(message) { console.log(message); } const greeting = { text: "Hello, world!" }; printMessage(greeting); // Prints "{ text: "Hello, world!" }"
编译后,它将生成以下 JavaScript 代码:
// JavaScript code function printMessage(message) { console.log(message); } var greeting = { text: "Hello, world!" }; printMessage(greeting); // Prints "{ text: "Hello, world!" }"
输出
上面的代码将产生以下输出:
{ text: 'Hello, world!' }
结论
TypeScript 中的结构类型是一个强大的特性,它提高了灵活性和代码可重用性以及互操作性。通过关注对象的结构而不是其显式类型,TypeScript 使开发者能够编写更具表现力和适应性强的代码。利用鸭子类型、实现多态性和享受灵活的类型兼容性的能力,在代码可维护性和可扩展性方面提供了显著的优势。在继续您的 TypeScript 之旅时,采用结构类型可以帮助您构建强大而灵活的应用程序。