當遇到需要告訴編譯器某個值是指定類型的場景時,我們可以使用類型斷言,比如這個例子:
const valueList = [123, "hello"] // getValue 函數隨機返回數字類型或者字符串類型 function getValue() { const num = Math.random() * 10 if (num < 5) { return valueList[0] } else { return valueList[1] } } const v = getValue() if (v.length) { // error,類型“string | number”上不存在屬性“length”。類型“number”上不存在屬性“length”。 console.log(v.length) } else { console.log(v.toFixed()) // 類型“string | number”上不存在屬性“toFixed”。類型“string”上不存在屬性“toFixed”。 }
這種情況在編譯階段報錯,可以使用類型斷言解決:
if ((v as string).length) { console.log((v as string).length) } else { console.log((v as number).toFixed()) }
使用類型斷言雖然可以解決這種需要指定類型的情況,但是顯得有些繁瑣,我們嘗試類型保護的方式來優化。
自定義類型保護
類型保護就是一些表達式,它們會在運行時檢查以確保在某個作用域里的類型。 要定義一個類型保護,我們只要簡單地定義一個函數,它的返回值是一個 “類型謂詞”。比如可以這樣定義一個類型保護函數:
function isString(value: number | string): value is string{ const num = Math.random() * 10 return num > 5 }
例子中的 value is string 就是類型謂詞,value 必須是參數中的一個。使用它也很方便:
const v = getValue() if (isString(v)) { console.log(v.length) } else { console.log(v.toFixed()) }
可以看到這比類型斷言更簡潔,只要檢查過一次類型,后續分支就不用檢查了,並且會自動推斷出 else 分支中的 v 是 number 類型。
typeof 類型保護
自定義類型保護需要定義一個函數來判斷類型,難免還是有些復雜,其實在 ts 中,如果是基本類型而不是復雜類型,可以直接使用 typeof 來做類型保護,如:
if (typeof v === 'string') { console.log(v.length) } else { console.log(v.toFixed()) }
這樣寫是可以的,效果和自定義類型保護一樣的。但它有一些限制,這些 typeof
類型保護只有兩種形式能被識別: typeof v === "typename"
和 typeof v !== "typename"
, "typename"
必須是 "number"
, "string"
, "boolean"
或 "symbol"
。 但是TypeScript並不會阻止你與其它字符串比較,語言不會把那些表達式識別為類型保護。
instanceof 類型保護
instanceof 操作符是 JS 中的原生操作符,用來判斷一個實例是不是某個構造函數創建的,或者是不是使用 es6 語法的某個類創建的。在 ts 中,使用 instanceof 操作符可以達到類型保護的效果。例子:
class Class1 { constructor(public name: string = 'aa') { } } class Class2 { constructor(public age: number = 18) { } } function getRandomItem() { return Math.random() > 0.5 ? new Class1() : new Class2() } const item = getRandomItem() if (item instanceof Class1) { console.log(item.name) } else { console.log(item.age) }
if 分支中使用 instanceof 判斷了 item,如果是 Class1 創建的,那么應該有 name 屬性,如果不是,那它就有 age 屬性。