Typescript高階型別與泛型難點詳解
最近做的TS分享,到了高階型別這一塊。通過琢磨和實驗還是挖掘出了一些深層的東西,在此處做一下記錄,也分享給各位熱愛前端的小夥伴。
其實在學習TS之前就要明確以下幾點:
1. typescript 是javascript的超集,這點是官方文件最先說明的,但是具體怎麼理解就千差萬別了。其實通俗的來說,ts語法就是基於js的一種通用規範,也就是js語法糖。
2. typescript是基於js的一門強型別的高階語言,而ts的所有新增語法都只是在編譯環境有效,都只是在編譯環境做的型別或語法的檢驗,而這些新增語法也都是為了使ts這門語言更方便地、更高效地進行大型專案的開發。而編譯出來的js檔案中也不會有半點ts的新增語法。因為是國際大廠造的輪子,所以程式碼的可執行性和相容性都依賴於ts的編譯器,這點我們不必過多擔心。
3. 就像是高階語言編譯為組合語言或再編譯為機器語言一樣,其實js也是要通過js直譯器去工作的,因為js本身就是一種解釋型語言,而js直譯器或市面上的多種瀏覽器核心以及chrome的v8引擎,都是再往更底層的程式碼去轉換。就像ts程式碼需要使用ts編譯器轉化為js程式碼一樣。只不過如果只接觸過js和ts兩種語言的話,可能就會覺得js是偏底層的程式碼了,但其實這麼理解是不對的。
下面迴歸到本次的正題:
1. ts中字面量的特殊性
大家熟悉了TS的型別相容性之後應該會有所瞭解,強型別語言中變數的賦值或引用是很嚴格的。
而在TS中,下面的寫法都不會報錯:
let test: {}; test = 1; test = null; test = false;
只舉幾個例子就可以了,也就是說所有型別都可以賦值為字面量型別。這就需要說到js中一切都是物件這個關鍵點了,js中,在生成變數時都會臨時new一個對應型別的包裝物件。所以與上文中字面量型別的test變數相容也就不足為奇了。
2. 交叉型別(&)與聯合型別(|)字面的意思所帶來的坑
可能會有人在聽到交叉型別時,會認為是兩種或多種型別的交集所產生的型別,首先要說明這麼理解是錯誤的。所以,其實交叉型別更多的帶有一些並集的意思,即新生成的型別,會擁有所有子型別的特性。而說起聯合型別,其實“聯合”二字,在不同的應用環境下,效果也是不盡相同的:
function printer (param: string | number) { console.log(param) }
做型別檢驗是聯合型別最常用的一種方式,此處意為string型別或number型別都可以通過校驗。
確定無誤後,帶著這個結論繼續往下看:
class Dog { eat () {} guardHome () {} } class Cat { eat () {} catchMice () {} } function animalFactory (): Dog | Cat { if (Math.random() * 10 > 5) { return new Dog(); } else { return new Cat(); } } let ani = animalFactory(); ani.guardHome(); // error ani.eat()
根據上面的結論, animalFactory 的返回值應該是Dog類或Cat類的例項都可以,但是卻偏偏只有eat方法能呼叫成功,屬於各自單獨類的guardHome或 catchMice方法都不能呼叫成功。
所以聯合型別在這種使用環境下,就是兩種型別的例項都能呼叫的成員變數或函式,在此處才可以按照字面的意思去理解。
3. 型別保護的特殊點
型別保護,顧名思義,即當前型別為保護型別,(假設)確定的型別。
那麼,在某個作用域內,到底從何時開始,對應變數開始成為保護型別了呢?
首先型別會出出現型別保護的三種情況:
(1) 型別斷言
(2) 使用型別謂詞進行自定義型別保護
(3) 使用typeof 和 instanceof
此方法就不過多說明
下面這種情況會幫助你更好的理解型別保護:
首先引數中的 string|number 是我們手動制定的型別要求,而在兩個大括號的型別保護區間內,我們發現只有當發生賦值或產生結果(如返回值)時,才會發生真正的型別保護,即型別推斷變為型別保護。
4. keyof(索引型別操作符)與泛型混合的多種寫法
首先 ,keyof 意為 某一資料型別的key的陣列集合,既適用於陣列,也適用於物件。下文會做驗證:
先來驗證陣列:
再來驗證物件:
此處有個需要特別注意的點:使用泛型如何表示某個特定key組成的陣列 :
上例中的 T[K][] 意為T型別的K組成的陣列,而且需要滿足,K為T的key
真正理解了上面這句話,自然就會明白下面四種寫法其實是等價的:
關於TS泛型和高階型別的新發現,持續更新中。。。