TypeScript 簡介

已發表: 2020-10-29

我編程的第一門語言是 C 和 Java。 使用這些語言,每次定義變量時都必須指定其類型。 如果你試圖給它分配另一個類型的值,編譯器會報錯:

 // main.c int main() { int x = "two"; return 0; } > cc main.c -o main // warning: initialization makes integer from pointer without a cast

問題是,由於我缺乏經驗,我從編譯器收到的許多投訴看起來都晦澀難懂。 這就是為什麼最後我將編譯器視為限制我創造力的工具,而實際上它應該是一個可以提供幫助的合作夥伴。

海綿鮑勃使電腦崩潰的 Gif

在我職業生涯的後期,我開始使用一些沒有強類型的編程語言,比如 JavaScript 或 PHP。 我認為它們非常酷:快速製作原型非常容易,無需處理挑剔的編譯器。

如您所知,WordPress 所基於的編程語言是 PHP 和 JavaScript。 這意味著您可能習慣於在沒有編譯器監視的情況下編寫插件和主題。 只有你、你的技能和你的創造力。 好吧,像這樣的錯誤:

 const user = getUser( userId ); greet( user.name ); // Uncaught TypeError: user is undefined

如果您厭倦了代碼中的類似undefined的錯誤,那麼是時候將編譯器添加到您的工作流程中了。 讓我們來看看 TypeScript 是什麼,以及它如何讓我們將軟件質量提高幾個數量級。

什麼是打字稿

TypeScript 是一種基於 JavaScript 的編程語言,旨在添加強類型和靜態類型。 TypeScript 類型允許我們描述對象和變量的形狀,從而產生更好的文檔和更健壯的代碼。 TypeScript 本身將負責驗證我們所做的一切。

按照設計,TypeScript 是 JavaScript 的超集。 這意味著任何用純 JavaScript 編寫的代碼,根據定義,也是有效的 TypeScript。 但反之則不然:如果您使用 TypeScript 特定的功能,則生成的代碼在您轉譯之前不是有效的 JavaScript。

TypeScript 的工作原理

要了解 TypeScript 的工作原理,我們將使用它的Playground ,這是一個小型編輯器,我們可以在其中編寫 TypeScript 代碼並查看編譯器告訴我們的內容。

作為 JavaScript 的超集,編寫 TypeScript 代碼非常容易。 基本上是以下 JavaScript 代碼:

 let user = "David"; let age = 34; let worksAtNelio = true; console.log( user, age, worksAtNelio );

也是 TypeScript 代碼。 您可以將其複制並粘貼到 TypeScript Playground 中,您將看到它已編譯。 那麼它有什麼特別之處呢? 它的類型,當然。 在 JavaScript 中,您可以執行以下操作:

 let user = "David"; let age = 34; let worksAtNelio = true; user = { name: "Ruth" }; console.log( user, age, worksAtNelio );

但這會在 TypeScript 中觸發錯誤。 只需在 Playground 中嘗試一下,您將看到以下錯誤:

 Type '{ name: string; }' is not assignable to type 'string'.

那是怎麼回事?

TypeScript 可以自動推斷變量的類型。 這意味著我們不需要明確地告訴它“嘿,這個變量是一個字符串”(或者一個數字,或者一個布爾值,或者其他什麼); 相反,它會查看第一次給出的值並據此推斷其類型。

有一個好主意的人的 Gif

在我們的示例中,當我們定義變量user時,我們為其分配了文本字符串"David" ,因此 TypeScript 知道user是(並且應該始終是)一個string 。 問題是稍後我們嘗試更改user變量的類型,方法是為其分配一個具有單個屬性( name )的對象,其值為字符串"Ruth" 。 顯然,這個對像不是string ,所以 TypeScript 抱怨並告訴我們不能執行賦值。

有兩種可能的方法來解決這個問題:

 // Option 1 let user = "David"; user = "Ruth"; // OK. // Option 2 let user = { name: "David" }; user = { name: "Ruth" };

讓我們在定義變量的類型時明確

但是類型推斷只是為了幫助我們。 如果我們願意,我們可以顯式地告訴 TypeScript 變量的類型:

 let user: string = "David"; let age: number = 34; let worksAtNelio: boolean = true; console.log( user, age, worksAtNelio );

它呈現與 TypeScript 推斷的類型完全相同的結果。 現在這引出了一個問題:如果 TypeScript 可以自動推斷類型,我們為什麼需要這個功能?

一方面,明確指定類型可以更好地記錄我們的代碼(在下一節中將變得更加清晰)。 另一方面,它允許我們解決類型推斷系統的局限性。 是的,你沒看錯:在某些情況下推理不是很好,TypeScript 能做的最好的事情就是告訴我們“好吧,我不知道這個變量到底應該是什麼; 我想它可以是任何東西!”

考慮以下示例:

 const getName = ( person ) => person.name; let david = { name: "David", age: 34 }; console.log( getName( david ) ); // Parameter 'person' implicitly has an 'any' type.

在這種情況下,我們定義了一個getName函數,它接收一個參數並返回一個值。 注意我們在這樣一個簡單的函數中假設了多少事情:我們期望一個對象( person )至少有一個名為name的屬性。 但是我們並不能保證調用這個函數的人會正確地使用它。 但即使他們這樣做了,我們仍然不知道這個函數的結果類型是什麼。 當然,您可能會假設name將是一個string ,但您之所以知道這一點,是因為您是人類並且您理解該詞的含義。 但是看看下面的例子:

 getName( "Hola" ); // Error getName( {} ); // Returns undefined getName( { name: "David" } ); // Returns a string getName( { name: true } ); // Returns a boolean

每次調用函數,我們都會得到不同的結果! 因此,儘管 TypeScript 一直在關注我們,但我們仍然遇到了經典的 JavaScript 問題:這個函數可以接收任何內容,也可以返回任何內容。

嬰兒非常悲傷的搞笑 gif

您猜對了,解決方案是顯式註釋函數。

定義自己的類型

但在此之前,讓我們看看另一個額外的 TypeScript 特性:自定義數據類型。 到目前為止,我們幾乎所有的示例都使用了基本類型,例如stringnumberboolean等,我認為這些類型很容易理解。 我們還看到了更複雜的數據結構,比如對象:

 let david = { name: "David", age: 34 };

但是我們並沒有過多關注 TypeScript 推斷的類型,是嗎? 我們所看到的只是由於鍵入不匹配而導致的無效分配的示例:

 let user = "David"; user = { name: "Ruth" }; // Type '{ name: string; }' is not assignable to type 'string'.

它以某種方式暗示對象的類型是“ {name:string;} ”,您可以將其理解為“這是一個具有稱為字符串類型名稱的屬性的對象”。 如果這是定義對像類型的方式,那麼以下應該是有效的 TypeScript:

 let david: { name: string, age: number } = { name: "David", age: 34 };

確實如此。 但我相信你會同意這並不方便。 幸運的是,我們可以在 TypeScript 中創建新類型。

一般來說,自定義類型基本上是具有自定義名稱的常規 TypeScript 類型:

 type Person = { name: string; age: number; }; let david: Person = { name: "David", age: 34 };

酷吧? 現在我們有了Person類型,我們可以輕鬆地註釋我們的getName函數並清楚地指定輸入參數的類型:

 const getName = ( person: Person ) => person.name;

而這個簡單的更新為 TypeScript 提供了很多信息! 例如,它現在可以推斷該函數的結果類型是string ,因為它肯定知道Person對象的name屬性也是string

 let david: Person = { name: "David", age: 34 }; let davidName = getName( david ); davidName = 2; // Type 'number' is not assignable to type 'string'.

但是,與往常一樣,如果您願意,可以顯式註釋結果類型:

 const getName = ( person: Person ): string => person.name;

更多關於 TypeScript 類型的東西……

最後,我想與您分享一些有趣的壯舉,如果您在 TypeScript 中定義自己的類型,您可以從中受益。

TypeScript 在為變量賦值時要求很高。 如果您已經明確定義了某個變量的類型,則只能分配與該類型完全匹配的值。 讓我們看幾個例子:

 type Person = { name: string; age: number; }; let david: Person = { name: "David", age: 34 }; // OK let ruth: Person = { name: "Ruth" }; // Property 'age' is missing in type '{ name: string; }' but required in type 'Person' let toni: Person = { name: "Toni", age: 35, gender: "M" }; // Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'. Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.

如您所見, Person變量只接受完全符合Person類型的對象。 如果缺少某些屬性( ruth沒有age )或屬性多於類型中包含的屬性( tonigender ),TypeScript 將抱怨並觸發類型不匹配錯誤。

但是,如果我們談論的是調用函數,事情就不同了! 函數中的參數類型不是一個確切的要求,但指定參數必須遵守的最小接口。 我知道,我知道,這太抽象了。 讓我們看一下下面的例子,我相信這會讓事情變得更清楚:

 type NamedObject = { name: string; }; const getName = ( obj: NamedObject ): string => obj.name; const ruth = { name: "Ruth" } getName( ruth ); // OK const david = { name: "David", age: 34 }; getName( david ); // OK const toni = { firstName: "Toni" }; getName( toni ); // Argument of type '{ firstName: string; }' is not assignable to parameter of type 'NamedObject'. Property 'name' is missing in type '{ firstName: string; }' but required in type 'NamedObject'.

如您所見,我們將getName函數重新定義為更通用的東西:它現在接受一個NamedObject對象,即必須具有名為string類型name的屬性的對象。 使用這個定義,我們可以看到ruthdavid如何完美地滿足這個要求(它們都有一個name屬性),但是toni沒有,因為它沒有期望屬性name

結論

TypeScript 是一種通過添加靜態類型定義來擴展 JavaScript 的編程語言。 這使我們在定義我們使用的數據時可以更加精確,更重要的是,它可以幫助我們更早地發現錯誤。

將 TypeScript 集成到開發堆棧中的成本相對較小,可以逐步完成。 由於根據定義,所有 JavaScript 代碼都是 TypeScript,因此從 JavaScript 切換到 TypeScript 是自動的——您可以一次添加類型並修飾您的代碼。

如果您喜歡這篇文章並想了解更多信息,請分享並在下面的評論部分告訴我。

國王教會國際在 Unsplash 上的特色圖片。