最近做的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) 類型斷言
let str; let val: number = (str as string).length; let val2: number = (<string>str).length;
(2) 使用類型謂詞進行自定義類型保護
let str; function checkString (str: any): str is string { return str; }
(3) 使用typeof 和 instanceof
此方法就不過多說明
下面這種情況會幫助你更好的理解類型保護:
function checkString (param: number | string) { if (typeof param === 'string') { let temp = param; //ok 此處param為string param += '1'; //ok 此處param為 string | number param += '1'; //ok 此處param為 string | number param += 1; // ok 此處string類型可以與數字相加 return param; // 此處param為number } else { // 此處此處相當於 if (typeof param === 'number') param += 1; // ok 此處param為 string | number param += '1'; // error number類型不能與字符串相加 return param // 此處param為number } }
首先參數中的 string|number 是我們手動制定的類型要求,而在兩個大括號的類型保護區間內,我們發現只有當發生賦值或產生結果(如返回值)時,才會發生真正的類型保護,即類型推斷變為類型保護。
4.
keyof(索引類型操作符)與泛型混合的多種寫法
首先 ,keyof 意為 某一數據類型的key的數組集合,既適用於數組,也適用於對象。下文會做驗證:
interface testInter { name: string, age: number } let testArr: string[] = ['tate', 'pomelott']; let testObj: testInter = {name: 'tate', age: 26}
先來驗證數組:
function showKey<K extends keyof T, T> (key: K, obj: Array<string>) { return key; } showKey<number, Array<string>>(1, testArr);
再來驗證對象:
function showKey<K extends keyof T, T> (keyItem: K, obj: T): K { return keyItem; } let val = showKey('name', testObj)
此處有個需要特別注意的點:使用泛型如何表示某個特定key組成的數組:
function showKey<K extends keyof T, T> (items: K[], obj: T): T[K][] { return items.map(item => obj[item]) }
上例中的 T[K][] 意為K類型的數組,而且需要滿足,K為T的key
真正理解了上面這句話,自然就會明白下面四種寫法其實是等價的:
function showKey<K extends keyof T, T> (items: K[], obj: T): T[K][] { return items.map(item => obj[item]) } function showKey<K extends keyof T, T> (items: K[], obj: T): Array<T[K]> { return items.map(item => obj[item]) } function showKey<K extends keyof T, T> (items: K[], obj: {[K in keyof T]: any}): K[] { return items.map(item => obj[item]) } function showKey<K extends keyof T, T> (items: K[], obj: {[K in keyof T]: any}): Array<K> { return items.map(item => obj[item]) } let obj = showKey(['name'], testObj)
關於TS泛型和高級類型的新發現,持續更新中。。。