使用 TypeScript 的好處
JavaScript 已經(jīng)很棒了,你或許會(huì)懷疑,我真的需要學(xué)習(xí) TypeScript 嗎?從技術(shù)層面上來(lái)說(shuō),成為一位出色的開(kāi)發(fā)者確實(shí)不需要學(xué)習(xí) TypeScript,大多數(shù)人沒(méi)有學(xué)習(xí) TypeScript 也做的很好。但是,工作中使用 TypeScript 確實(shí)有許多好處:
- 基于靜態(tài)類(lèi)型,用 TypeScript 編輯代碼有更高的預(yù)測(cè)性,更易糾錯(cuò)。
- 由于模塊,命名空間和強(qiáng)大的面向?qū)ο缶幊讨С?,使?gòu)建大型復(fù)雜應(yīng)用程序的代碼庫(kù)更加容易。
- TypeScript在編譯為JavaScript的過(guò)程中,在它到達(dá)運(yùn)行時(shí)間前可以捕獲所有類(lèi)型的錯(cuò)誤,并中斷它們的執(zhí)行。
- Angular 2 框架就是用 TypeScript 編寫(xiě)的,同時(shí)推薦開(kāi)發(fā)人員在項(xiàng)目中也使用這種語(yǔ)言。
安裝TypeScript
安裝 TypeScript 最簡(jiǎn)單的方式就是通過(guò) npm。使用以下命令行,可以全局安裝 TypeScript 包,然后就可以在所有項(xiàng)目中使用TypeScript編譯器了:
npm install -g typescript
打開(kāi)終端然后運(yùn)行 tsc -v 命令來(lái)查看是否正確安裝了 TypeScript.
tsv -v
Version 3.8.1
第一個(gè)例子
新建一個(gè) greeter.ts文件
function greeter(person) {
return "Hello, " + person;
}
let user = "Jane User";
document.body.innerHTML = greeter(user);
編譯成 JavaScript
TypeScript 是 寫(xiě)在 .ts 文件(或者 JSX的.tsx)里,不能直接在瀏覽器端運(yùn)行,需要首先轉(zhuǎn)譯為vanilla.js。這個(gè)編譯的過(guò)程可以有多種實(shí)現(xiàn)方式:
- 在終端上運(yùn)行前面提到的命令行工具
tsc。 - 直接在 Visual Studio 或者其他 IDE 和文本編輯器上(操作)。
- 使用自動(dòng)化構(gòu)建工具,
tsc greeter.ts
*這行命令是把 TypeScript 文件 index.ts編譯為 JavaScript 版本的 greeter.js。如果 greeter.js 已經(jīng)存在的話會(huì)被覆蓋。
也可以通過(guò)列出所有的文件或者使用通配符來(lái)一次編譯多個(gè)文件:
#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
靜態(tài)類(lèi)型
TypeScript 一個(gè)很獨(dú)特的特征是支持靜態(tài)類(lèi)型。意思就是可以聲明變量的類(lèi)型,(因此)編譯器就可以確保賦值時(shí)不會(huì)產(chǎn)生類(lèi)型錯(cuò)誤。如果省略了類(lèi)型聲明,TypeScript 將會(huì)從代碼中自動(dòng)推測(cè)出正確的類(lèi)型。
Number
//number
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
String
let color: string = "blue";
color = 'red';
也可以混合使用
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ fullName }.
I'll be ${ age + 1 } years old next month.`;
Array
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
Tuple
可以在一個(gè)數(shù)組中分別定義數(shù)據(jù)的類(lèi)型
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error
Enum
來(lái)自JavaScript的標(biāo)準(zhǔn)數(shù)據(jù)類(lèi)型集的一種有用的補(bǔ)充則是枚舉。就像C#這樣的語(yǔ)言中,枚舉是一種為數(shù)值集提供更友好名稱(chēng)的方法。
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
枚舉的一個(gè)便利的功能是,您還可以從數(shù)值轉(zhuǎn)到枚舉中該值的名稱(chēng)。例如,如果我們的值為2,但不確定上面的Color枚舉中映射到了什么,我們可以查找相應(yīng)的名稱(chēng):
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
console.log(colorName); // Displays 'Green' as its value is 2 above
Any
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)
let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
如果你知道這種類(lèi)型的某些部分,那么任何類(lèi)型都很方便,但也有例外情況。例如,您可能有一個(gè)數(shù)組,但該數(shù)組有不同類(lèi)型的混合:
let list: any[] = [1, true, "free"];
list[1] = 100;
Void
function warnUser(): void {
console.log("This is my warning message");
}
Null and Undefined
// Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;
Never
// Function returning never must have unreachable end point
function error(message: string): never {
throw new Error(message);
}
// Inferred return type is never
function fail() {
return error("Something failed");
}
// Function returning never must have unreachable end point
function infiniteLoop(): never {
while (true) {
}
}
以下是一些最常用的數(shù)據(jù)類(lèi)型:
- Number (數(shù)值類(lèi)型) – 所有數(shù)字都是數(shù)值類(lèi)型的,無(wú)論是整數(shù)、浮點(diǎn)型或者其他數(shù)值類(lèi)型都相同。
- String(字符串類(lèi)型) – 文本類(lèi)型,就如 vanilla JS 字符串一樣可以使用單引號(hào)或者雙引號(hào)。
- Boolean(布爾類(lèi)型) – true 或者 false,用 0 和 1 會(huì)造成編譯錯(cuò)誤。
- Tuple (元組)– 元組類(lèi)型允許表示一個(gè)已知元素?cái)?shù)量和類(lèi)型的數(shù)組,各元素的類(lèi)型不必相同
- Any(任意類(lèi)型) – 該類(lèi)型的變量可以設(shè)定為字符串類(lèi)型,數(shù)值類(lèi)型或者任何其他類(lèi)型。
- Enum (枚舉) - 對(duì)JavaScript標(biāo)準(zhǔn)數(shù)據(jù)類(lèi)型的一個(gè)補(bǔ)充。 像C#等其它語(yǔ)言一樣,使用枚舉類(lèi)型可以為一組數(shù)值賦予友好的名字。
- Arrays(數(shù)組類(lèi)型) – 有兩種語(yǔ)法:my_arr: number[];或者my_arr: Array<number>
- Void (空類(lèi)型)- 用在不返回任何值的函數(shù)中。
- Null 和 Undefined - TypeScript里,undefined和null兩者各自有自己的類(lèi)型分別叫做undefined和null。 和 void相似,它們的本身的類(lèi)型用處不是很大。
- Never - 表示的是那些永不存在的值的類(lèi)型。
接口通常會(huì)根據(jù)一個(gè)對(duì)象是否符合某種特定結(jié)構(gòu)來(lái)進(jìn)行類(lèi)型檢查。通過(guò)定義一個(gè)接口我們可以命名一個(gè)特殊的組合變量,確保它們會(huì)一直一起運(yùn)行。當(dāng)轉(zhuǎn)譯成 JavaScript 時(shí),接口會(huì)消失 – 它們唯一的目的是在開(kāi)發(fā)階段里起到輔助的作用。
在下面的例子中我們定義了一個(gè)簡(jiǎn)單的接口來(lái)對(duì)一個(gè)函數(shù)自變量進(jìn)行類(lèi)型檢查:
// 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);
類(lèi)
在搭建大型規(guī)模的應(yīng)用程序時(shí),尤其是在 Java 或 C# 當(dāng)中,許多開(kāi)發(fā)者會(huì)優(yōu)先選擇面向?qū)ο缶幊?。TypeScript 提供一個(gè)類(lèi)系統(tǒng),和 Java、C# 中的非常相似,包括了繼承,抽象類(lèi),接口實(shí)現(xiàn),setters/getters 方法等。
值得一提的是由于最新的 JavaScript 更新(ECMAScript 2015),這些類(lèi)對(duì)于 vanilla JS 來(lái)說(shuō)是原生的,并且在沒(méi)有 TypeScript 的情況下也可以使用。這兩種實(shí)現(xiàn)方式非常相似但是也有不同的地方,TypeScript 更加嚴(yán)格一些。
繼續(xù)上面的 food的 例子,這里有一個(gè)簡(jiǎn)單的TypeScript類(lèi):
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();
只要寫(xiě)過(guò)一點(diǎn) Java 或者 C# ,就會(huì)發(fā)現(xiàn)TypeScript和它們?cè)谡Z(yǔ)法上非常相似。繼承也是一樣:
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();
泛型
泛型(Generics)是允許同一個(gè)函數(shù)接受不同類(lèi)型參數(shù)的一種模板。相比于使用 any 類(lèi)型,使用泛型來(lái)創(chuàng)建可復(fù)用的組件要更好,因?yàn)榉盒蜁?huì)保留參數(shù)類(lèi)型。
一段簡(jiǎn)單的腳本例子,傳入一個(gè)參數(shù),返回一個(gè)包含了同樣參數(shù)的數(shù)組。
// 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
第一次調(diào)用函數(shù)的時(shí)候,我們將類(lèi)型手動(dòng)設(shè)置成字符串。第二次及以后再次調(diào)用的時(shí)候就不必這樣做了,因?yàn)榫幾g器會(huì)判斷傳遞過(guò)什么參數(shù)并且自動(dòng)決定哪種類(lèi)型最適合。雖然不是強(qiáng)制性的,但是由于編譯器在眾多復(fù)雜環(huán)境中確定正確類(lèi)型的時(shí)候可能會(huì)失敗,所以每次都傳入類(lèi)型是好的做法。
在開(kāi)發(fā)大型應(yīng)用時(shí),另一個(gè)重要的概念是模塊化。與一個(gè)有 10000 行代碼的文件相比,把代碼分成多個(gè)可復(fù)用組件,這樣可以幫助項(xiàng)目保持條理性和易懂性。
TypeScript 介紹了導(dǎo)入和導(dǎo)出模塊的語(yǔ)句,但是并不能解決文件間的真正連接。TypeScript 依賴于第三方函數(shù)庫(kù)來(lái)加載外部模塊:用于瀏覽器應(yīng)用程序的 require.js 和用于 Node.js 的 CommonJS。我們來(lái)看一個(gè)簡(jiǎn)單的帶有 require.js 的TypeScript 模塊例子:
我們會(huì)有兩個(gè)文件。一個(gè)是導(dǎo)出函數(shù),另一個(gè)是導(dǎo)入并調(diào)用函數(shù)。
exporter.ts
var sayHi = function(): void {
console.log("Hello!");
}
export = sayHi;
importer.ts
import sayHi = require('./exporter');
sayHi();
現(xiàn)在我們需要下載 require.js,包含在一個(gè)script標(biāo)簽里 – 如何設(shè)置請(qǐng)點(diǎn)擊這里。最后一步是編譯這兩個(gè) .ts 文件。需要添加一個(gè)額外的參數(shù)來(lái)告訴 TypeScript,我們是為 require.js 創(chuàng)建模塊的(也被稱(chēng)為AMD),而不是 CommonJS。
tsc --module amd *.ts
以上就是有關(guān)TypeScript的基本知識(shí)。