TypeScript 10分鐘快速入門

TypeScript 是一個以 JavaScript 語言為基礎,所延伸出來的另一種超集的程式語言,也就是說 TypeScript 涵蓋了幾乎目前 ECMAScript 標準的新式語言範圍,並額外加了許多關於資料類型的預先定義與相互檢查部份。

學習 TypeScript 需要有對新式的 ECMAScript 的一定熟悉程度才能相得益章、共取其利,不然只是讓你的學習之路備感艱難。

TypeScript 是為了解決什麼問題而誕生的?

這個問題的由來是因為 JavaScript 是一種動態資料類型的程式語言,動態資料類型代表在程式碼中,變數會自動依照指定值變更資料類型,這是直譯式腳本語言的常見特性,有容易學習與使用的優點,但也是非常大的缺點。類型的使用與轉換,常常都是程式碼中潛在的問題,例如開發者經常會因為指定值的類型錯誤,造成不可預期的結果,或是有些函式庫在使用時,如果沒有仔細觀看文件,甚至或是文件寫得不清不楚,也容易造成資料類型誤用的情況。

TypeScript 加入靜態資料類型在原本的 JavaScript 後,代表可以讓變數或資料定義時,就可以指定必需使用哪一種資料類型,之後在使用時如果發生資料類型不相符時,就會發出預先的警告,這對於開發具有一定規模化的 JavaScript 應用程式時,更具有強健性相當有幫助。

註:在這篇部落格文章:Flow靜態資料類型的檢查工具,10分鐘快速入門中可以參考Flow工具與TypeScript的比較。

環境安裝 & 第一次的編譯體驗

一開始學習 TypeScript,你只需要下面兩個東西,不需要花太多時間建立環境,因為你的首要重點是在於學習它是什麼,和能幫你作什麼而已:

Node 安裝好後,裡面會包含 npm 套件管理程式,請打開命令列工具(終端機)視窗輸入以下的指令:

npm install -g typescript

安裝成功後,你可以在命令列中輸入tsc,應該會看到目前的TypeScript編譯器的版本和各種資訊,像下面這樣:

❯ tsc
Version 3.9.7
//...

試著打開 Visual Studio Code 或其它你撰寫 JavaScript 的工具程式,然後貼上以下的程式碼,存成一個test.ts檔案:

function foo(name : string) {
return 'Hello ' + name
}
foo(123)

然後將命令列工具的指令,切換到這個檔案的目錄下,然後輸入tsc指令來進行編譯,像下面這樣:

❯ tsc test.ts

接著可以看到 TypeScript 編譯器報錯的訊息,這個錯誤是說123與我們要傳入foo所需的參數資料類型不符:

❯ tsc test.ts
test.ts:5:5 - error TS2345: Argument of type '123' is not assignable to parameter of type 'string'.
5 foo(123)
~~~
Found 1 error.

當然,手動編譯是一件無趣的事情,我們在實際專案開發時也不會這樣來作這件事情。

這裡只是要說明,TypeScript的檔案都是以.ts為副檔名,最終編譯為.js檔案,理所當然的,它在編譯的過程中就會執行它的檢查工作,尤其是對類型有使用上的問題,它是回報錯誤,但還是會讓你編譯成功,最後還是會產生一個test.js的檔案。

如果你想要在編譯時,如果有錯誤發生就不要產出.js檔,你可以用下面的指令,也就是多加了--noEmitOnError

tsc --noEmitOnError test.ts

這裡我要先說明的是,TypeScript的編譯器與設定選項比你想像中的內容還多了幾十倍,相互的設定關聯也複雜了幾十倍。初學入門花太多時間在這上面,會有點浪費時間而且也沒多大意思,我們主要學習的是它能作什麼,和該怎麼作,然後是不是在你要作的事情上能協助這些事情。這些設定選項我們可以一開始先用別人設定好的,和幾個常用到的,其它的細部設定,等你哪天有需要再回頭來查詢即可。

由簡單的範例開始說明

我們回頭看一下剛剛上面的程式碼:

function foo(name : string) {
return 'Hello ' + name
}
foo(123)

唯一與你學過的 JavaScript 不同之處,就是foo函式的傳入參數後面多了一個:string的指示詞,這東西在 TypeScript 中稱為"Type Annotation/A諾貼遜/",中文是"類型註解"的意思。

這意思是說,這個傳入參數我們希望它是會個string(字串)類型,不可以是別的資料類型。所以在foo(123)這個函式呼叫或執行時,TypeScript 就幫我們抓出了錯誤,因為是傳入參數的資料類型錯誤。

對於函式而言,實際在 TypeScript 中要撰寫函式需要養成習慣,函式會有傳入參數和回傳值的兩種情況,像上述的範例,應該要寫成像下面這樣:

function foo(name : string) : string {
return 'Hello ' + name
}
foo(123)

上面這代表foo的回傳值也是一個string類型,如果函式沒有回傳值的話,你可以用void這個 TypeScript為函式專門設計的沒有回傳值用的類型,實際上 JavaScript的函式就算你沒寫,也是會回傳undefined就是了。

當然傳入或回傳的類型註解有可能會有很多種,或是還有傳入參數預設值的寫法,所以我們的函式就會寫得複雜得多了,像下面這樣的程式碼範例:

function foo(name : string | number = 'Eddy') : string {
if(typeof name === 'number')
return 'Hello Somebody'
return 'Hello ' + name
}
foo('Bob') // Hello Bob
foo(123) // Hello Somebody
foo() //Hello Eddy

上面可以看到我們的傳入參數的類型註解變得複雜多了,像這樣(name : string | number = 'Eddy'),意思是可以是numberstring,然後預設值是Eddy字串。

聯集類型(Union/尤里恩/ Types)是用這個符號豎線(|),也就是或(or)的意思,另外還有一個用符號和號(&),稱為交叉類型(Intersection/英特謝遜/ Types),也就是與(and)的意思,通常會用在物件的類型定義時。(官網文件(英文)舊文件(簡中))

上面只是簡單的介紹而已,有關於函式類型的詳細內容,可以再參考TypeScript官網手冊中,有關函式的章節(官網文件(英文)舊文件(簡中))。

介面(Interface)

對於物件中屬性的類型定義,顯得更複雜得多了,這是學習的重點中的重點,在這上面的學問相當的多,細節也很多。TypeScript 使用了介面來描述物件的外形(Shape)樣子,以下面的範例程式碼來說明:

interface User {
name : string
id : number
username?: string
}
const user1 : User = {
name: "Eddy",
id: 0
}
const user2 : User = {
name: "Jobs",
username: "jobs123",
id: 0
}

要注意的是interface的宣告是一行一個屬性定義,後面並沒有逗號(,),而是要加分號(;)或不加都可以,這只是撰寫風格的差異而已(要不要用分號作語句結尾)。像下面這樣的程式碼範例:

interface User {
name : string
id : number
username?: string
}

interface User {
name : string;
id : number;
username?: string;
}

上面可以看到username?這個屬性後面加了一個符號問號(?),代表它是可選的(optional),也就是"可有可無的"的意思。

介面與物件的內容比你想像也多很多,它們有很多與其它像類型、函式間的交互應用,可以參考官網文件

類別

ES6開始讓 JavaScript有了類別(class)的定義方式,但內容非常的陽春,之後的一直到ES這幾年的標準中,加入了不少新的有關於類別的新特性內容。

TypeScript 對於ES中的新特性都可以支援得很好,也有自己擴充的有關於類型定義的特性,簡單來說它讓類別整個的定義更為完整,例如用於定義成員的public、private與protected修飾字等等,以及像是static(靜態)屬性或abstract(抽象)類別等等。一個簡單的類別範例,像下面這樣的範例程式碼:

class User {
private name : string
constructor(name : string) {
this.name = name
}
greet() {
console.log(`Hello, ${this.name}!`)
}
}
const eddy = new User("Eddy")
eddy.name // Error: 'name' is private
eddy.greet()

新的ES2019 (ES10)中加入了Private fields(私有欄位),長得就不太像上面那樣,而是像下面的範例程式碼,TypeScript也是支援的:

class User {
#name : string
constructor(name : string) {
this.#name = name;
}
greet() {
console.log(`Hello, ${this.#name}!`);
}
}
const eddy = new User("Eddy")
eddy.name // Error: 'name' is private
eddy.greet()

上面兩個範例的差異在於,TypeScript 可以編譯第一個程式碼,也就是自己的private為ES5標準的JavaScript,但第二個則是只能編譯為ES2015後的版本,所以還是有些差異的部份,太新的ES標準真要在編譯為ES5標準的JavaScript時,或許babel才是一個正確的選擇。

結語

最後,我們對 TypeScript 的認識只是剛開始而已,你可以把它當作是 JavaScript 語言的升級版本,或是擴充版本,TypeScript是為了要能符合規模化應用程式開發,所必要學習的另一條道路。

就上面的內容中所提及的,TypeScript的內容遠比你想像的的多,官方的文件提供了很好的參考資料,但並不是整個都要看完學完,才是真正學會它。學習基本的,然後其它在需要的時候來查閱,這才是學習的正確方式。

其它參考