03-ts-泛型

這是我參與更文挑戰的第4天,活動詳情查看: 更文挑戰web


泛型

identity函數。 這個函數會返回任何傳入它的值markdown

function identity(arg: any): any {
    return arg;
}

複製代碼

使用any類型會致使這個函數能夠接收任何類型的arg參數,這樣就丟失了一些信息:傳入的類型與返回的類型應該是相同的。若是咱們傳入一個數字,咱們只知道任何類型的值都有可能被返回。ide

所以,咱們須要一種方法使返回值的類型與傳入參數的類型是相同的。 這裏,咱們使用了 類型變量,它是一種特殊的變量,只用於表示類型而不是值。

function identity<T>(arg: T): T {
    return arg;
}

複製代碼

咱們給identity添加了類型變量T。 T幫助咱們捕獲用戶傳入的類型(好比:number),以後咱們就可使用這個類型。 以後咱們再次使用了 T當作返回值類型。如今咱們能夠知道參數類型與返回值類型是相同的了。 這容許咱們跟蹤函數裏使用的類型的信息。函數

咱們把這個版本的identity函數叫作泛型,由於它能夠適用於多個類型。 不一樣於使用 any,它不會丟失信息,像第一個例子那像保持準確性,傳入數值類型並返回數值類型。post

定義了泛型函數後,能夠用兩種方法使用。 第一種是,傳入全部的參數,包含類型參數:ui

let output = identity<string>("myString"); 
複製代碼

第二種方法更廣泛。利用了類型推論 -- 即編譯器會根據傳入的參數自動地幫助咱們肯定T的類型:url

let output = identity("myString");
複製代碼

注意咱們不必使用尖括號(<>)來明確地傳入類型;編譯器能夠查看myString的值,而後把T設置爲它的類型。 類型推論幫助咱們保持代碼精簡和高可讀性。若是編譯器不可以自動地推斷出類型的話,只能像上面那樣明確的傳入T的類型,在一些複雜的狀況下,這是可能出現的。spa

泛型變量

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}


function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

複製代碼

泛型類型

泛型函數的類型與非泛型函數的類型沒什麼不一樣,只是有一個類型參數在最前面,像函數聲明同樣:3d

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <T>(arg: T) => T = identity;

複製代碼

咱們還可使用帶有調用簽名的對象字面量來定義泛型函數:code

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: {<T>(arg: T): T} = identity;
複製代碼

這引導咱們去寫第一個泛型接口了。 咱們把上面例子裏的對象字面量拿出來作爲一個接口:

interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn = identity;
複製代碼

一個類似的例子,咱們可能想把泛型參數看成整個接口的一個參數。 這樣咱們就能清楚的知道使用的具體是哪一個泛型類型(好比: GenericIdentityFn<string>而不是GenericIdentityFn)。 這樣接口裏的其它成員也能知道這個參數的類型了。

interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;
複製代碼

當咱們使用 GenericIdentityFn的時候,還得傳入一個類型參數來指定泛型類型(這裏是:number),鎖定了以後代碼裏使用的類型。 對於描述哪部分類型屬於泛型部分來講,理解什麼時候把參數放在調用簽名裏和什麼時候放在接口上是頗有幫助的。

除了泛型接口,咱們還能夠建立泛型類。 注意,沒法建立泛型枚舉和泛型命名空間。

泛型約束

前面的例子,咱們想訪問arg的.length屬性,可是編譯器並不能證實每種類型都有length屬性,因此就報錯了。

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}
複製代碼

如今咱們把泛型約束一下,限制函數去處理任意帶有.length屬性的全部類型。只要傳入的類型有這個屬性,咱們就容許,就是說至少包含這一屬性。 爲此,咱們須要列出對於T的約束要求。

改寫以下:

interface Length {
    length: number
}

function loggingIdentity<T extends Length>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

複製代碼

如今這個泛型函數被約束了類型, 所以它再也不適用於任意類型:

例如:

loggingIdentity(123) // Error, number doesn't have a .length property

複製代碼

咱們須要傳入符合約束類型的值,必須包含必須的屬性:

loggingIdentity({ length: 1, num: 1 })
複製代碼
相關文章
相關標籤/搜索