不管是昨天提到的元組跟列舉,還是更早之前的型別別名、介面,我們在做的,一直都是將變數/參數/各類值限縮到特定型別,將我們在編譯時可能發生錯誤的機率降到最低。在第五天的時候,我們有簡單提到型別防衛(Type guard),今天讓我們回過頭來,講講在函式的參數、或函式內部,我們該怎麼進行限縮型別,以達到使用TypeScript的最佳效果。
我們來看個初步的例子吧:
function sayHiXTimes(times: number | string,name:string){
return "hi".repeat(times) + " " + name
}
已經有了基本的型別概念後,我們可以很快發現,times
有可能是數字或字串,而.repeat
並非同時存在於兩種型別的方法,所以TypeScript會對我們吼:Argument of type 'string | number' is not assignable to parameter of type 'number'. Type 'string' is not assignable to type 'number'
Okok,很合理,既然如此我們就在函式內明確寫個判斷式,如果times的型別是字串就怎樣、不然就怎樣,就能將所有情境處理掉了。
function sayHiXTimes(times: number | string, name: string) {
if (typeof times === "number") {
return "hi".repeat(times) + " " + name
}
return "hi " + name + " this is wrong arguments"
}
console.log(sayHiXTimes(5, "John"))
// hihihihihi John
console.log(sayHiXTimes("5", "John"))
// hi John this is wrong arguments
這邊寫起來真的超無聊的,我們多寫了一個判斷式,讓整段程式碼變得有些冗長,但這也正是TypeScript的精神。TypeScript會盡量為每一條可能的路徑,去找到他最準確會回傳的值/型別。因此他看到了typeof ...
,便知道這邊我們只看其中一條路徑,如果我們只寫完if
而沒有寫出第二個return,TypeScript便會告訴我們:Not all code paths return a value.
,代表我們沒有考慮到全部的可能,有一條路徑可能沒有回傳值。
typeof
只是我們看到的其中一個型別防衛的方法,滿直白好懂的。但我們來看一個會讓他故障的例子:
function printWords(messages: string | string[] | null) {
if (typeof messages === "string") {
console.log(messages)
} else if (typeof messages === "object") {
messages.forEach(message => console.log(message))
} else {
console.log("You gave me nothing!")
}
}
你看出哪邊有問題了嗎?陣列是object
,null
也是object
唷!所以在我們想對messages
使用陣列特有的方法.forEach
時,其實也有可能是對null
使用.forEach
,想當然就不可能正常運作啦。這邊我們可以再用一個判斷式,來判斷該值存不存在,也就是說,不存在的情況下,我們在操作的就是null,存在的情況下,我們操作的就是陣列或字串。
function printWords(messages: string | string[] | null) {
if (messages) {
if (typeof messages === "string") {
console.log(messages)
} else if (typeof messages === "object") {
messages.forEach(message => console.log(message))
}
}
else {
console.log("here's nothing")
}
}
以上面這個寫法,我們透過if(messages)
看messages
有沒有值,所以TypeScript覺得我們有考慮到所有路徑了,就不會報錯。
(寫出這種TypeScript的同時,我們還能順便複習一下,到底JavaScript會將什麼值轉譯成false
(0
、NaN
、null
、undefined
、""
這類的)。)
Ok,你應該又發現問題了。空字串在上面的例子中,當作參數其實應該也是一個合法的字串吧,但我們在if(messages)
裡面,在轉換的過程中會將空字串當作false
,所以我們的程式碼執行到了不該執行的地方。
上面這邊講到的是最基本的限縮方式,其實還有更多,像是透過instanceof
來判斷某一個值是否來自特定類別衍生出來的、或用in
關鍵字來判斷一個物件內有沒有特定的屬性/方法等。但這些觀念都差不多,就是要將型別限制在特定範圍內,好讓開發者提前找到錯誤。
但,這邊還要特別注意,函式內的任何值的型別被限縮後,是可能再轉變成別的型別的,所以我們要更注意值在整個函式內的變化(下面擷取自官網中的範例):
小結:
你會想開始學TypeScript,上述關於型別防衛、關於限縮,其實你早就知道了。只是TypeScript會再幫我們想的更多一些,畢竟JavaScript作為弱型別的語言,變數型別可能說變就變,今天講的,大概就是讓我們再多思考型別在程式碼中的變化。你說不知道這些會不會怎樣?我覺得差異不大,但多知道一點總是好的,免得哪天TypeScript在檢查你的程式碼時衝康了你,要了解情況就耗時一些。