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) 類型斷言
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泛型和高級類型的新發現,持續更新中。。。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM