TypeScript 是微軟開發(fā)和控制的開源項目,我在應用 Anguar 2+ 和 Ionic 2+ 框架開發(fā)系統(tǒng)時使用的就是 TypeScript,但我一直把 TypeScript 當作 JavaScript 來用的,甚至是照貓畫虎。
所以我想正兒八經(jīng)的學習下 TypeScript,弄明白我糊里糊涂使用的東西,這篇 Chat 分享算是我的一個學習筆記,適合于有一定 JavaScript 基礎的人,在此基礎上學習 TypeScript 的主要差異。
0. 背景
0.1. 什么是 TypeScript
TypeScript 是一種由微軟開發(fā)的自由和開源的編程語言,它是 JavaScript 的一個超集,而且本質(zhì)上向這個語言添加了可選的靜態(tài)類型和基于類的面向?qū)ο缶幊獭?/p>
TypeScript 擴展了 JavaScript 的語法,所以任何現(xiàn)有的 JavaScript 程序可以不加改變的在 TypeScript 下工作。
這就是為什么我一直用 Javascript 開發(fā) TypeScript 的 Angular 的原因:TS 是 JS 的一個超集。
0.2. 使用 TypeScript 的優(yōu)勢
為什么要學習 TypeScript 了,實際上 JavaScript 已經(jīng)夠用了,而且像我沒學過 TypeScript 也照樣開發(fā)得很好,我們來看看它的優(yōu)勢:
更多的規(guī)則和類型限制,讓代碼預測性更高,可控性更高,易于維護和調(diào)試。
對模塊、命名空間和面向?qū)ο蟮闹С?,更容易組織代碼開發(fā)大型復雜程序。
TypeScript 的編譯步驟可以捕獲運行之前的錯誤。
Angular2+ 和 Ionic2+ 默認使用 TypeScript(我就是工作中接觸了這兩個框架才開始學習 TS 的,而且這兩個框架我感覺挺有前途的。)。
1. 安裝和環(huán)境搭建
安裝需要 Node.js 和 npm,Node.js 自帶 npm 這里就不深入了,假設我們已經(jīng)安裝好了 Node.js。
TypeScript 的安裝使用也很簡單,官網(wǎng)首頁簡單粗暴的給出了一些信息:
首先,通過 npm 全局安裝 TypeScript:
npm i -g typescript
我安裝完成是這個樣子,注意-g 參數(shù),表示是全局安裝,這樣我們在其他工程中也能使用 TypeScript 的命令行編譯工具。
安裝完成后我們可以使用 TypeScript 的編譯工具 tsc 驗證一下:
tsc -v Version 2.6.2
打開控制臺窗口,其中運行 tsc -v 打印出安裝的版本號。
1.1. 第一次接觸
有很多 IDE 支持 TypeScript 的編寫,你可以找一個你喜歡的,我用的是 VSCode,免費好用不折騰。
我們先來寫第一個 TyepScript 代碼,就抄官網(wǎng)上的:
function greeter(person) {return "Hello, " + person; }let user = "Jane User";document.body.innerHTML = greeter(user);
保存為 main.ts,然后編譯:
tsc main.ts
此時,tcs 將把 main.ts 翻譯成 JavaScript 代碼,并在當前目錄下保存為同名的 js 文件 main.js。
tsc 還支持一次編譯多個文件列表或者使用通配符編譯全部文件,例如:
tsc main.ts mod.ts
編譯文件夾下所有 ts 文件:
tsc *.ts
還有一個高級的玩法是監(jiān)聽文件變化,當發(fā)生改變時自動編譯,這樣我們在學習 TS 的時候?qū)懸幌拢缓蠛芊奖愕目纯词鞘裁匆馑迹?/p>
tsc main.ts --watch
1.2. 使用 webpack 搭建環(huán)境
Webpack 是一個前端資源加載 / 打包工具,它將根據(jù)模塊的依賴關系進行靜態(tài)分析,然后將這些模塊按照指定的規(guī)則生成對應的靜態(tài)資源。
項目開發(fā)中有很多文件,依賴也各不相同,使用 webpack 可以方便的組織管理我們的代碼。
1.2.1 初始化工程
現(xiàn)在把這個目錄變成 npm 項目,初始化語句如下:
npm init
你會看到一些提示,放心地使用默認值就可以了。 當然,你也可以隨時到生成的 package.json 文件里修改。
package.json 配置
{ "name": "code", "version": "1.0.0", "description": " 測試 ", "main": "main.ts", "scripts": { "test": "test", }, "keywords": [ "testcode" ], "author": "sunsi", "license": "ISC", "devDependencies": { } }
1.2.2. 安裝配置 Webpack
首先確保已經(jīng)全局安裝了 Webpack。
npm install -g webpack webpack-dev-server
Webpack 這個工具可以將你的所有代碼和可選擇地將依賴捆綁成一個單獨的 .js 文件。
webpack-dev-server,可以讓我們在開發(fā)的時候啟動一個 web 服務用于運行測試網(wǎng)頁程序。
安裝好后需要編輯 webpack.config.js 以適應項目,因為多數(shù)項目開發(fā)有著一定的共性,有大同小異或者是約定俗成的配置,所以我們可以把精力集中在開發(fā)上,而不用糾結(jié)代碼的組織。
module.exports = { // 入口文件 entry: "./src/main.ts", // 輸出文件和目錄 output: { filename: "bundle.js", path: __dirname + "/dist" }, // 打開 sourcemaps 調(diào)試 webpack 的輸出 devtool: "source-map", resolve: { // 添加 '.ts' 和 '.tsx' 后綴可以被處理 extensions: [".ts", ".tsx", ".js", ".json", ".html"] }, module: { rules: [ // '.ts' or '.tsx' 后綴的文件將被 loadr 'awesome-typescript-loader' 處理。 { test: /.tsx?/, loader: "source-map-loader" } ] } };
npm 可以簡化我們的命令行,將 package.json 的 scripts 修改,例如:
"scripts": { "build": "webpack", "dev2": "webpack-dev-server --client-log-level none --color --inline --hot --config webpack.dev.js", "dev": "webpack-dev-server --client-log-level none --color --inline --hot" }
上面的代碼讓我沒可以使用 npm 的命令簡化執(zhí)行。
執(zhí)行 npm run build 相當于使用 webpack 命令編譯;執(zhí)行 npm run dev 相當于啟動一個服務。
1.2.3. 安裝配置 TypeScript
接下來,我們要添加開發(fā)時依賴 awesome-typescript-loader 和 source-map-loader,有時也用 ts-loader 的。
npm install --save-dev typescript awesome-typescript-loader source-map-loader
這些依賴會讓 TypeScript 和 webpack 在一起良好地工作,webpack 可以通過很多第三方的 loader 來擴展功能,awesome-typescript-loader 可以讓 Webpack 使用 TypeScript 的標準配置文件 tsconfig.json 編譯 TypeScript 代碼。
source-map-loader 使用 TypeScript 輸出的 sourcemap 文件來告訴 webpack 何時生成自己的 sourcemaps,這就允許你在調(diào)試最終生成的文件時就好像在調(diào)試 TypeScript 源碼一樣。
注意我們安裝 TypeScript 為一個開發(fā)依賴,也就是開發(fā)的時候需要運行的時候不需要。 我們還可以使用 npm link typescript 來鏈接 TypeScript 到一個全局拷貝,但這不是常見用法。
然后,我們配置 TypeScript,編寫一個文件:
{ "compilerOptions": { "outDir": "./dist/", "sourceMap": true, "noImplicitAny": true, "module": "commonjs", "target": "es5" }, "include": [ "./src/*/" ] }
json 文件不能添加注釋,但這些簡單的配置一看即知。
1.2.4. 組織代碼和目錄
最后,我們的 package.json 變成這個樣子:
package.json 配置
{ "name": "code", "version": "1.0.0", "description": " 測試 ", "main": "main.ts", "scripts": { "build": "webpack", "dev2": "webpack-dev-server --client-log-level none --color --inline --hot --config webpack.dev.js", "dev": "webpack-dev-server --client-log-level none --color --inline --hot" }, "keywords": [ "testcode" ], "author": "sunsi", "license": "ISC", "devDependencies": { "awesome-typescript-loader": "^3.4.1", "source-map-loader": "^0.2.3", "typescript": "^2.6.2" } }
剛才,我們?yōu)殚_發(fā)環(huán)境編寫了一些配置文件,最終的目錄應該是這樣的:
\PrjName // 項目根目錄 | index.html // 網(wǎng)頁,將引用 dist/bundle.js | package-lock.json // webpack 自己生成的文件,現(xiàn)在忽略 | package.json // npm init 配置文件 | tsconfig.json // TypeScript 配置文件 | webpack.config.js // webpack 默認配置文件 | +---dist // 輸出目錄 | bundle.js // 輸出文件 | bundle.js.map // 輸出 map 文件 | ---src // 源碼目錄 main.ts // 源碼,也是入口文件
下面我們新建 src 目錄、index.html 和 main.ts 文件:
在 Main.ts 中使用 TypeScript 編寫代碼
function greeter(person:string) { return "Hello, " + person; } let user = "TypeScript User"; document.body.innerHTML = greeter(user);
最后,我們運行測試下代碼,在命令行中輸入:
npm run build npm run dev
瀏覽器中輸入 http://localhost:8080,可以看到:
[圖片上傳失敗...(image-ea6a79-1534226186080)]
2. TypeScript 的類型
假如我們已經(jīng)使用過 JavaScript,那么你要知道 TypeScript 最大的不同就是 Type,這是我們這次分享的重點,我們得花點時間來感受下類型。
2.1. 靜態(tài)類型
TypeScript 的一個非常顯著的特點是靜態(tài)類型的支持,這意味著您可以聲明變量的類型,并且編譯器將確保它們沒有被分配到錯誤類型的值。如果省略類型聲明,它們將從您的代碼中自動推斷出來。
看看下面的代碼,就是就是 JavaScript 再加上類型定義而已:
function greeter(person: string) {return "Hello, " + person; }let user = "Jane User";document.body.innerHTML = greeter(user);
由于 JavaScript 是不 care 類型的,所以編譯后的代碼類型被移除了,編譯后變成我們熟悉的 JavaScript:
function greeter(person) {return "Hello, " + person; }var user = "Jane User";document.body.innerHTML = greeter(user);
如果我們把一個數(shù)組傳入方法:
function greeter(person: string) {return "Hello, " + person; }let user = [0, 1, 2];document.body.innerHTML = greeter(user);
這時就會產(chǎn)生一個錯誤:
main.ts(7,35): error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'. 15:34:56 - Compilation complete. Watching for file changes.
TypeScript 提供了靜態(tài)的代碼分析,它可以分析代碼結(jié)構(gòu)和提供的類型注解。要注意的是盡管有錯誤,main.js 文件還是被創(chuàng)建了。
就算你的代碼里有錯誤,你仍然可以使用 TypeScript。但在這種情況下,TypeScript 會警告你代碼可能不會按預期執(zhí)行。
很明顯,TypeScript 就是一個有型的 JavaScript。
2.2. 基礎類型
這里列出一些常用的數(shù)據(jù)類型:
- 布爾值
最基本的數(shù)據(jù)類型就是簡單的 true/false 值,在 JavaScript 和 TypeScript 里叫做 boolean(其它語言中也一樣),使用 0 和 1 將導致編譯錯誤。
let isDone: boolean = false;
- 數(shù)字
和 JavaScript 一樣,TypeScript 里的所有數(shù)字都是浮點數(shù),這些浮點數(shù)的類型是 number。
除了支持十進制和十六進制字面量,TypeScript 還支持 ECMAScript 2015 中引入的二進制和八進制字面量。沒有分別定義為 integers、floats 或是其它值。
let decLiteral: number = 6;let hexLiteral: number = 0xf00d;let binaryLiteral: number = 0b1010;let octalLiteral: number = 0o744;
- 字符串
JavaScript 程序的另一項基本操作是處理網(wǎng)頁或服務器端的文本數(shù)據(jù)。
像其它語言里一樣,我們使用 string 表示文本數(shù)據(jù)類型,和 JavaScript 一樣,可以使用雙引號( “)或單引號(’)表示字符串。
let name: string = "bob"; name = "smith";
開發(fā)過程中拼字符串是最常用的了,使用模版字符串比 format 更加強大,它可以定義多行文本和內(nèi)嵌表達式。
這種字符串是被反引號包圍( `)(鍵盤布局左上方數(shù)字鍵 1 左邊),可以通過 ${ expr } 這種形式嵌入表達式
let name: string = Gene;let age: number = 37;let sentence: string = Hello, my name is ${ name }. I'll be ${ age + 1 } years old next month.;
這與下面加號拼接的方式效果相同,但是明顯字符串模版更簡單強大:
let sentence: string = "Hello, my name is " + name + ".\n\n" +"I'll be " + (age + 1) + " years old next month.";
- 數(shù)組
TypeScript 像 JavaScript 一樣可以操作數(shù)組元素,有兩種方式可以定義數(shù)組。
第一種,可以在元素類型后面接上 [],表示由此類型元素組成的一個數(shù)組:
let list: number[] = [1, 2, 3];
第二種方式是使用數(shù)組泛型,Array<元素類型>:
let list: Array<number> = [1, 2, 3];
let list: any[] = [1, true, "free"]; list[1] = 100;
- Any
有時候,我們會想要為那些在編程階段還不清楚類型的變量指定一個類型,這些值可能來自于動態(tài)的內(nèi)容,比如來自用戶輸入或第三方代碼庫。
這種情況下,我們不希望類型檢查器對這些值進行檢查而是直接讓它們通過編譯階段的檢查,那么我們可以使用 any 類型來標記這些變量:
let notSure: any = 4; notSure = "maybe a string instead"; notSure = false; // okay, definitely a boolean
在對現(xiàn)有代碼進行改寫的時候,any 類型是十分有用的,它允許你在編譯時可選擇地包含或移除類型檢查。
你可能認為 Object 有相似的作用,就像它在其它語言中那樣。 但是 Object 類型的變量只是允許你給它賦任意值 - 但是卻不能夠在它上面調(diào)用任意的方法,即便它真的有這些方法:
let notSure: any = 4; notSure.ifItExists(); // okay, ifItExists might exist at runtimenotSure.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'.
具有這種類型的變量可以將它的值設置為字符串、數(shù)字或其他任何東西。
- Void
用于不返回任何東西的函數(shù)。
某種程度上來說,void 類型像是與 any 類型相反,它表示沒有任何類型。 當一個函數(shù)沒有返回值時,你通常會見到其返回值類型是 void:
function warnUser(): void { alert("This is my warning message"); }
聲明一個 void 類型的變量沒有什么大用,因為你只能為它賦予 undefined 和 null:
let unusable: void = undefined;
- Null 和 Undefined
TypeScript 里,undefined 和 null 兩者各自有自己的類型分別叫做 undefined 和 null。和 void 相似,它們的本身的類型用處不是很大:
// Not much else we can assign to these variables!let u: undefined = undefined;let n: null = null;
默認情況下 null 和 undefined 是所有類型的子類型,就是說你可以把 null 和 undefined 賦值給 number 類型的變量。
然而,當你指定了—strictNullChecks 標記,null 和 undefined 只能賦值給 void 和它們各自,這能避免很多常見的問題。
也許在某處你想傳入一個 string 或 null 或 undefined,你可以使用聯(lián)合類型 string | null | undefined。
注意:盡可能地使用—strictNullChecks。
- Never
never 類型表示的是那些永不存在的值的類型。
例如, never 類型是那些總是會拋出異?;蚋揪筒粫蟹祷刂档暮瘮?shù)表達式或箭頭函數(shù)表達式的返回值類型; 變量也可能是 never 類型,當它們被永不為真的類型保護所約束時。
never 類型是任何類型的子類型,也可以賦值給任何類型;然而,沒有類型是 never 的子類型或可以賦值給 never 類型(除了 never 本身之外),即使 any 也不可以賦值給 never。
下面是一些返回 never 類型的函數(shù):
// 返回 never 的函數(shù)必須存在無法達到的終點function error(message: string): never {throw new Error(message); }// 推斷的返回值類型為 neverfunction fail() {return error("Something failed"); }// 返回 never 的函數(shù)必須存在無法達到的終點function infiniteLoop(): never {while (true) { } }
2.3. 類型轉(zhuǎn)換
好比其它語言里的類型轉(zhuǎn)換,但是不進行特殊的數(shù)據(jù)檢查和解構(gòu),它對運行時時沒有影響,只在編譯時起作用。
TypeScript 會假設你知道自己在干什么,已經(jīng)進行了必須的檢查。
有兩種形式。 其一是 “尖括號” 語法:
let someValue: any = "this is a string";let strLength: number = (<string>someValue).length;
另一個為 as 語法:
let someValue: any = "this is a string";let strLength: number = (someValue as string).length;
兩種形式是等價的,至于使用哪個大多數(shù)情況下是憑個人喜好;然而,當你在 TypeScript 里使用 JSX 時,只有 as 語法斷言是被允許的。