跳至主要内容

[TS] 一般型別

TypeScript 的安裝與編譯

  1. 安裝 TypeScript 編譯器。
npm install -g typescript
  1. 建立一個 ts 檔案,如:hello.ts。
  2. 輸入:
function greet(person: string){
console.log(`Hello, ${person}`)
}

greet('world')
  1. 打開終端機輸入:tsc hello.ts
  2. 執行編譯過後出現 JavaScript 檔案。

自動編譯

前面已經先全局安裝過 TypeScript 了,並且新增了 hello.ts 檔案來練習 TS 語法並透過 tsc hello.js 來編譯 TS 檔案。
但在實務上通常要開始寫 TS 前會先在一個 tsconfig.json 的檔案中做編譯設定,前面只是為了快速練習 TS 語法所以先跳過這部分。

tsc --init

執行完畢,tsconfig.json 會出現在專案資料中。

打開設定檔,先把那些林林總總的註解拿掉,會看到 TS 的預設是這樣:

tsconfig.json
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
  1. target:指定編譯生成的 JavaScript 的版本。
  2. module:指定了生成模組的格式。CommonJS 是 Node.js 中常見的模組系統。
  3. esModuleInterop:允許 TypeScript 對 CommonJS 模組進行更好的交互操作。
  4. forceConsistentCasingInFileNames:TypeScript 將嚴格檢查文件名的大小寫一致性。
  5. strict:啟用所有的嚴格類型檢查選項
  6. skipLibCheck:跳過對宣告文件 (第三方套件) 的檢查。

基本上只用預設設定 TS 也能動,但可以客製化讓 TS 更方便,而那一大包註解就是 TS 允許調整的設定內容。

最後說一下自動編譯。當我在學 TS 每次在那邊 tsc hello.ts 時都在想,SCSS 有自動檢測編譯的功能,TS 有沒有?
還真的有,在終端機輸入 tsc --watch 就可以讓 TS 每次在儲存檔案時就自動編譯。而像我一樣 VScode 設定每次變動都自動儲存的人,這就等同無時無刻都在自動編譯了。

TypeScript 的基本操作

型別註記

TypeScript 的出現就是為了解決 JavaScript 型別組織鬆散的問題。所以 TypeScript 的核心就是讓我們手動為我們的變數們加上型別,然後由 TypeScript 為我們做型別的把關,避免因為型別而出現一些非預期的錯誤。
比方說下面這個很經典的算術例子:

JavaScript
const sum = (x, y) =>{
return x + y
}

console.log(sum(1, '1'))

預期只是數字相加,但在程式碼某處突然傳入一個字串,就會產生這種 1 + '1' = 11 的錯誤了,這是 JavaScript 型別轉換的鍋。

改成 TypeScript:

const sum = (x: number, y: number): number =>{
return x + y
}

console.log(sum(1, '1'))

在編譯時 TypeScript 就會跳出來說:Argument of type 'string' is not assignable to parameter of type 'number'.
上述的例子就是為參數和函式的 return 做了型別註記,TypeScript 就會依照註記去做檢查。

對於 JavaScript 的基礎型別:booleannumberstringnullundefinedsymbol,通通可以用像下面這個例子這麼簡單的方式做型別註記:

let isDone: boolean = false
危險

TypeScript 提供一個型別叫做 any,代表什麼型別都可以,請盡量不要使用,不然就喪失 TypeScript 的使用意義了。

型別推論

let myName = 'Jeremy'

如果在第一次宣告變數時有給予值,其實 TypeScript 就隱性地為這個變數加上一個型別了。

聯合型別

聯合型別就是讓我們的變數可以儲存多種型別中的一種:

let num: number | string 
num = 9
num = 'nine'
注意

TypeScript 只能存取聯合型別中各型別共有的方法。

const getLength = (x: string | number):number =>{
return x.length
}

TypeScript 丟了一個錯誤:Property 'length' does not exist on type 'string | number'.
那是因為 length 不是 number prototype 下的方法,但如果是像 toString 這種兩者都有的方法就可以編譯成功。
這在實務上有時滿討厭的,一方面雖然擴充了變數接受的型別,但另一方面卻又限制了我們能使用的方法,會變成需要看情況來做型別斷言。

型別斷言

手動指定一個值的型別,白話一點就是為參數指定在什麼型別下要幹什麼事。以用在前面的聯合型別為例。

wrong answer
const getLength = (x: string | number):number =>{
if(x.length){
return x.length
}else{
return x.toString().length
}
}

依照上面聯合型別知道的,這東西一定會噴錯,但如果加上型別斷言就不一樣:

right answer
const getLength = (x: string | number):number =>{
if((x as string).length){
return (x as string).length
}else{
return x.toString().length
}
}
注意

這裡要補充說明一點,assertion 沒事真的少用,因為它的行為其實講白話就是我們直接跟 TypeScript 說:我知道這個東西是什麼型別的,你不用管了,這樣 TypeScript 就會忽略了它的型別檢查,其實危險度相當於 any
那什麼時候可以用?
像上面開宗明義說的,用在有聯合型別時,你很確定什麼時候該用什麼型別時,這時候就可以用 assertion 來告訴 TypeScript。

Reference

  1. TypeScript 新手指南
  2. TypeScript 邁向專家之路 / Adam Freeman 著 / 許文達 譯 / 旗標出版