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 之旅時,採用結構型別可以幫助您構建強大而靈活的應用程式。