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方法都不能調用成功。javascript

因此聯合類型在這種使用環境下,就是兩種類型的實例都能調用的成員變量或函數,在此處才能夠按照字面的意思去理解。
 
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);

再來驗證對象:java

function showKey<K extends keyof T, T> (keyItem: K, obj: T): K {
    return keyItem;
}
let val = showKey('name', testObj)

此處有個須要特別注意的點:使用泛型如何表示某個特定key組成的數組chrome

function showKey<K extends keyof T, T> (items: K[], obj: T): T[K][] {
    return items.map(item => obj[item])
}

上例中的  T[K][] 意爲K類型的數組,並且須要知足,K爲T的keytypescript

真正理解了上面這句話,天然就會明白下面四種寫法實際上是等價的:數組

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

相關文章
相關標籤/搜索