在 30 分鐘內學習 TypeScript
今天我們來看看 TypeScript,這是一種編譯成 JavaScript 的語言,專為構建大型複雜應用程序的開發人員而設計。它從 C# 和 Java 等語言中繼承了許多編程概念,為原本非常輕鬆和自由類型的 JavaScript 添加了更多的紀律和秩序。
本教程面向相當精通 JavaScript 但在 TypeScript 方面仍是初學者的人。我們已經涵蓋了大部分基礎知識和關鍵特性,同時包含了許多帶有註釋代碼的示例,以幫助您了解該語言的實際應用。開始吧!
使用 TypeScript 的好處
JavaScript 本身就很好,您可能想知道 我真的需要學習 TypeScript 嗎? 從技術上講,您不需要 要學習 TypeScript 成為一名優秀的開發人員,大多數人沒有它就可以了。然而,使用 TypeScript 肯定有它的好處:
- 由於靜態類型,使用 TypeScript 編寫的代碼更容易預測,並且通常更容易調試。
- 借助模塊、命名空間和強大的 OOP 支持,可以更輕鬆地為大型和復雜的應用組織代碼庫。
- TypeScript 有一個 JavaScript 編譯步驟,可以在各種錯誤到達運行時並破壞某些東西之前捕獲它們。
- 即將推出的 Angular 2 框架是用 TypeScript 編寫的,建議開發人員在他們的項目中也使用這種語言。
最後一點實際上對許多人來說是最重要的,也是讓他們進入 TypeScript 的主要原因。 Angular 2 是目前最熱門的框架之一,儘管開發人員可以使用常規 JavaScript,但大多數教程和示例都是用 TS 編寫的。隨著 Angular 2 社區的擴展,自然會有越來越多的人使用 TypeScript。
安裝 TypeScript
設置 TypeScript 最簡單的方法是通過 npm。使用下面的命令,我們可以全局安裝 TypeScript 包,讓 TS 編譯器在我們所有的項目中都可用:
npm install -g typescript
嘗試在任何地方打開終端並運行 tsc -v
查看是否已正確安裝。
tsc -v Version 1.8.10
支持 TypeScript 的文本編輯器
TypeScript 是一個開源項目,但由 Microsoft 開發和維護,因此最初僅在 Microsoft 的 Visual Studio 平台中受支持。如今,有更多的文本編輯器和 IDE 原生或通過插件提供對 TypeScript 語法、自動完成建議、錯誤捕獲甚至內置編譯器的支持。
- Visual Studio Code - Microsoft 的另一個輕量級開源代碼編輯器。內置 TypeScript 支持。
- Sublime Text 的官方免費插件。
- 最新版本的 WebStorm 帶有內置支持。
- 更多,包括 Vim、Atom、Emacs 等。
編譯成 JavaScript
TypeScript 是用 .ts 編寫的 文件(或 JSX 的 .tsx),不能直接在瀏覽器中使用,需要先翻譯成 vanilla .js。這個編譯過程可以通過多種不同的方式完成:
- 在終端使用前面提到的命令行工具
tsc
. - 直接在 Visual Studio 或其他一些 IDE 和文本編輯器中。
- 使用自動任務運行程序,例如 gulp。
我們發現第一種方法最簡單且對初學者最友好,因此我們將在課程中使用它。
以下命令採用名為 main.ts 的 TypeScript 文件 並將其翻譯成它的 JavaScript 版本 main.js .如果 main.js 已經存在會被覆蓋。
tsc main.ts
我們還可以通過列出所有文件或應用通配符來一次編譯多個文件:
# Will result in separate .js files: main.js worker.js. tsc main.ts worker.ts # Compiles all .ts files in the current folder. Does NOT work recursively. tsc *.ts
我們也可以使用 --watch
進行更改時自動編譯 TypeScript 文件的選項:
# Initializes a watcher process that will keep main.js up to date. tsc main.ts --watch
更高級的 TypeScript 用戶還可以創建一個 tsconfig.json 文件,由各種構建設置組成。在處理具有大量 .ts 文件的大型項目時,配置文件非常方便,因為它在某種程度上自動化了該過程。你可以閱讀更多關於 tsconfig.json 在此處的 TypeScript 文檔中
靜態輸入
TypeScript 的一個非常顯著的特點是對靜態類型的支持。這意味著您可以聲明變量的類型,編譯器將確保它們沒有被分配錯誤的值類型。如果省略類型聲明,它們將自動從您的代碼中推斷出來。
這是一個例子。任何變量、函數參數或返回值都可以在初始化時定義其類型:
var burger: string = 'hamburger', // String calories: number = 300, // Numeric tasty: boolean = true; // Boolean // Alternatively, you can omit the type declaration: // var burger = 'hamburger'; // The function expects a string and an integer. // It doesn't return anything so the type of the function itself is void. function speak(food: string, energy: number): void { console.log("Our " + food + " has " + energy + " calories."); } speak(burger, calories);
因為 TypeScript 是編譯成 JavaScript,而後者不知道是什麼類型,所以完全去掉了:
// JavaScript code from the above TS example. var burger = 'hamburger', calories = 300, tasty = true; function speak(food, energy) { console.log("Our " + food + " has " + energy + " calories."); } speak(burger, calories);
但是,如果我們嘗試做一些非法的事情,在編譯 tsc
將警告我們代碼中存在錯誤。例如:
// The given type is boolean, the provided value is a string. var tasty: boolean = "I haven't tried it yet";
main.ts(1,5): error TS2322: Type 'string' is not assignable to type 'boolean'.
如果我們將錯誤的參數傳遞給函數,它也會警告我們:
function speak(food: string, energy: number): void{ console.log("Our " + food + " has " + energy + " calories."); } // Arguments don't match the function parameters. speak("tripple cheesburger", "a ton of");
main.ts(5,30): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
以下是一些最常用的數據類型:
- 數字 - 所有數值都由數字類型表示,整數、浮點數或其他沒有單獨的定義。
- String - 文本類型,就像普通 JS 中的字符串一樣,可以用“單引號”或“雙引號”括起來。
- 布爾值 -
true
或false
, 使用 0 和 1 會導致編譯錯誤。 - Any - 此類型的變量可以將其值設置為字符串、數字或 any 別的東西。
- 數組 - 有兩種可能的語法:
my_arr: number[];
或my_arr: Array<number>
. - Void - 用於不返回任何內容的函數。
要查看所有可用類型的列表,請轉到官方 TypeScript 文檔 - 此處。
接口
接口用於類型檢查對像是否適合特定結構。通過定義接口,我們可以命名變量的特定組合,確保它們總是在一起。翻譯成 JavaScript 後,接口就消失了——它們的唯一目的是在開發階段提供幫助。
在下面的示例中,我們定義了一個簡單的接口來對函數的參數進行類型檢查:
// Here we define our Food interface, its properties, and their types. interface Food { name: string; calories: number; } // We tell our function to expect an object that fulfills the Food interface. // This way we know that the properties we need will always be available. function speak(food: Food): void{ console.log("Our " + food.name + " has " + food.calories + " calories."); } // We define an object that has all of the properties the Food interface expects. // Notice that types will be inferred automatically. var ice_cream = { name: "ice cream", calories: 200 } speak(ice_cream);
屬性的順序無關緊要。我們只需要存在所需的屬性 並成為正確的類型 .如果缺少某些內容、類型錯誤或名稱不同,編譯器會警告我們。
interface Food { name: string; calories: number; } function speak(food: Food): void{ console.log("Our " + food.name + " has " + food.calories + " grams."); } // We've made a deliberate mistake and name is misspelled as nmae. var ice_cream = { nmae: "ice cream", calories: 200 } speak(ice_cream);
main.ts(16,7): error TS2345: Argument of type '{ nmae: string; calories: number; } is not assignable to parameter of type 'Food'. Property 'name' is missing in type '{ nmae: string; calories: number; }'.
這是一個初學者指南,所以我們不會詳細介紹接口。但是,它們比我們在這裡提到的要多得多,所以我們建議您查看 TypeScript 文檔 - 在這裡。
類
在構建大型應用程序時,許多開發人員更喜歡面向對象的編程風格,尤其是在 Java 或 C# 等語言中。 TypeScript 提供了一個與這些語言中的類系統非常相似的類系統,包括繼承、抽像類、接口實現、setter/getter 等等。
值得一提的是,自從最近的 JavaScript 更新 (ECMAScript 2015) 以來,類是原生 JS 並且可以在沒有 TypeScript 的情況下使用。這兩個實現非常相似,但也有區別,TypeScript 更嚴格一些。
繼續食物主題,這是一個簡單的 TypeScript 類:
class Menu { // Our properties: // By default they are public, but can also be private or protected. items: Array<string>; // The items in the menu, an array of strings. pages: number; // How many pages will the menu be, a number. // A straightforward constructor. constructor(item_list: Array<string>, total_pages: number) { // The this keyword is mandatory. this.items = item_list; this.pages = total_pages; } // Methods list(): void { console.log("Our menu for today:"); for(var i=0; i<this.items.length; i++) { console.log(this.items[i]); } } } // Create a new instance of the Menu class. var sundayMenu = new Menu(["pancakes","waffles","orange juice"], 1); // Call the list method. sundayMenu.list();
任何至少寫過一點 Java 或 C# 的人都應該覺得這種語法很熟悉。繼承也是如此:
class HappyMeal extends Menu { // Properties are inherited // A new constructor has to be defined. constructor(item_list: Array<string>, total_pages: number) { // In this case we want the exact same constructor as the parent class (Menu), // To automatically copy it we can call super() - a reference to the parent's constructor. super(item_list, total_pages); } // Just like the properties, methods are inherited from the parent. // However, we want to override the list() function so we redefine it. list(): void{ console.log("Our special menu for children:"); for(var i=0; i<this.items.length; i++) { console.log(this.items[i]); } } } // Create a new instance of the HappyMeal class. var menu_for_children = new HappyMeal(["candy","drink","toy"], 1); // This time the log message will begin with the special introduction. menu_for_children.list();
要更深入地了解 TS 中的類,您可以閱讀文檔 - 此處。
泛型
泛型是允許同一個函數接受各種不同類型的參數的模板。使用泛型創建可重用組件比使用 any
更好 數據類型,因為泛型保留了進出它們的變量的類型。
一個簡單的例子是一個腳本,它接收一個參數並返回一個包含相同參數的數組。
// The <T> after the function name symbolizes that it's a generic function. // When we call the function, every instance of T will be replaced with the actual provided type. // Receives one argument of type T, // Returns an array of type T. function genericFunc<T>(argument: T): T[] { var arrayOfT: T[] = []; // Create empty array of type T. arrayOfT.push(argument); // Push, now arrayOfT = [argument]. return arrayOfT; } var arrayFromString = genericFunc<string>("beep"); console.log(arrayFromString[0]); // "beep" console.log(typeof arrayFromString[0]) // String var arrayFromNumber = genericFunc(42); console.log(arrayFromNumber[0]); // 42 console.log(typeof arrayFromNumber[0]) // number
第一次調用該函數時,我們手動將類型設置為字符串。這不是必需的,因為編譯器可以查看已傳遞的參數並自動決定哪種類型最適合它,就像在第二次調用中一樣。雖然不是強制性的,但每次都提供類型被認為是一種很好的做法,因為在更複雜的場景中編譯器可能無法猜測出正確的類型。
TypeScript 文檔包括幾個高級示例,包括泛型類、將它們與接口相結合等等。你可以在這裡找到它們。
模塊
處理大型應用程序時的另一個重要概念是模塊化。與將所有內容都放在一個 10000 行的文件相比,將您的代碼拆分為許多小的可重用組件有助於您的項目保持井井有條且易於理解。
TypeScript 引入了用於導出和導入模塊的語法,但無法處理文件之間的實際連接。要啟用外部模塊,TS 依賴於第三方庫:瀏覽器應用程序的 require.js 和 Node.js 的 CommonJS。讓我們看一個帶有 require.js 的 TypeScript 模塊的簡單示例:
我們將有兩個文件。一個導出一個函數,另一個導入並調用它。
exporter.ts
var sayHi = function(): void { console.log("Hello!"); } export = sayHi;
importer.ts
import sayHi = require('./exporter'); sayHi();
現在我們需要下載 require.js 並將其包含在腳本標籤中 - 看這裡如何。最後一步是編譯我們的兩個 .ts 文件。需要添加一個額外的參數來告訴 TypeScript 我們正在為 require.js(也稱為 AMD)構建模塊,而不是 CommonJS 模塊。
tsc --module amd *.ts
模塊非常複雜,超出了本教程的範圍。如果您想繼續閱讀有關它們的信息,請前往 TS 文檔 - 此處。
第三方聲明文件
當使用最初為常規 JavaScript 設計的庫時,我們需要應用聲明文件以使該庫與 TypeScript 兼容。聲明文件的擴展名為 .d.ts 並包含有關該庫及其 API 的各種信息。
TypeScript 聲明文件通常是手工編寫的,但您需要的庫很可能已經有 .d.ts。別人創建的文件。 DefinitiveTyped 是最大的公共存儲庫,包含超過一千個庫的文件。還有一個流行的 Node.js 模塊,用於管理 TypeScript 定義,稱為 Typings。
如果您仍需要自己編寫聲明文件,本指南將幫助您入門。
TypeScript 2.0 中即將推出的功能
TypeScript 仍在積極開發中,並且不斷發展。在撰寫本教程時,LTS 版本是 1.8.10,但微軟已經發布了 TypeScript 2.0 的 Beta。現已公開測試,您可以立即試用:
npm install -g [email protected]
它引入了一些方便的新概念,例如:
- 不可為空的類型標誌,可防止某些變量的值設置為
null
或undefined
. - 使用
npm install
直接獲取聲明文件的新改進系統 . - 控制流類型分析,可捕獲編譯器之前遺漏的錯誤。
- 模塊導出/導入語法的一些創新。
另一個期待已久的功能是能夠在 async/await
中控制異步函數的流程 堵塞。這應該在未來的 2.1 更新中可用。
進一步閱讀
官方文檔中的信息量起初可能有點讓人不知所措,但通過它的好處將是巨大的。我們的教程將用作介紹,因此我們沒有涵蓋 TypeScript 文檔中的所有章節。以下是我們跳過的一些更有用的概念:
- 命名空間 - 在這裡。
- 枚舉 - 在這裡。
- 高級類型和類型保護 - 在這裡。
- 在 TypeScript 中編寫 JSX - 在這裡。
結論
我們希望您喜歡本教程!
你對 TypeScript 有什麼想法嗎?你會考慮在你的項目中使用它嗎?歡迎在下方留言!