1. 程式人生 > >TypeScript筆記 :泛型(六)

TypeScript筆記 :泛型(六)

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

泛型型別

泛型函式的型別與非泛型函式的型別沒什麼不同,只是有一個型別引數在最前面,像函式宣告一樣:

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

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

我們也可以使用不同的泛型引數名,只要在數量上和使用方式上能對應上就可以。

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

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

我們還可以使用帶有呼叫簽名的物件字面量來定義泛型函式:

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;

一個相似的例子,我們可能想把泛型引數當作整個介面的一個引數。 這樣我們就能清楚的知道使用的具體是哪個泛型型別(比如: Dictionary<string>而不只是Dictionary)。 這樣接口裡的其它成員也能知道這個引數的型別了。

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

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

let myIdentity: GenericIdentityFn<number> = identity;

注意,我們的示例做了少許改動。 不再描述泛型函式,而是把非泛型函式簽名作為泛型型別一部分。 當我們使用GenericIdentityFn的時候,還得傳入一個型別引數來指定泛型型別(這裡是:number),鎖定了之後程式碼裡使用的型別。 對於描述哪部分型別屬於泛型部分來說,理解何時把引數放在呼叫簽名裡和何時放在介面上是很有幫助的。

泛型類

泛型類看上去與泛型介面差不多。 泛型類使用( <>)括起泛型型別,跟在類名後面。

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

GenericNumber類的使用是十分直觀的,並且你可能已經注意到了,沒有什麼去限制它只能使用number型別。 也可以使用字串或其它更復雜的型別。

let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };

console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));

與介面一樣,直接把泛型型別放在類後面,可以幫助我們確認類的所有屬性都在使用相同的型別。

我們在類那節說過,類有兩部分:靜態部分和例項部分。 泛型類指的是例項部分的型別,所以類的靜態屬性不能使用這個泛型型別。

泛型約束

有時候想操作某型別的一組值,並且我們知道這組值具有什麼樣的屬性。 在loggingIdentity例子中,我們想訪問arglength屬性,但是編譯器並不能證明每種型別都有length屬性,所以就報錯了。

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

相比於操作any所有型別,我們想要限制函式去處理任意帶有.length屬性的所有型別。 只要傳入的型別有這個屬性,我們就允許,就是說至少包含這一屬性。 為此,我們需要列出對於T的約束要求。

為此,我們定義一個介面來描述約束條件。 建立一個包含 .length屬性的介面,使用這個介面和extends關鍵字來實現約束:

interface Lengthwise {
    length: number;
}

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

相比於操作any所有型別,我們想要限制函式去處理任意帶有.length屬性的所有型別。 只要傳入的型別有這個屬性,我們就允許,就是說至少包含這一屬性。 為此,我們需要列出對於T的約束要求。

為此,我們定義一個介面來描述約束條件。 建立一個包含 .length屬性的介面,使用這個介面和extends關鍵字來實現約束:

interface Lengthwise {
    length: number;
}

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

現在這個泛型函式被定義了約束,因此它不再是適用於任意型別:

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

我們需要傳入符合約束型別的值,必須包含必須的屬性:

loggingIdentity({length: 10, value: 3});

在泛型約束中使用型別引數

你可以宣告一個型別引數,且它被另一個型別引數所約束。 比如,現在我們想要用屬性名從物件裡獲取這個屬性。 並且我們想要確保這個屬性存在於物件 obj上,因此我們需要在這兩個型別之間使用約束。

function getProperty(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

在泛型裡使用類型別

在TypeScript使用泛型建立工廠函式時,需要引用建構函式的類型別。

function create<T>(c: {new(): T; }): T {
    return new c();
}

一個更高階的例子,使用原型屬性推斷並約束建構函式與類例項的關係。

class BeeKeeper {
    hasMask: boolean;
}

class ZooKeeper {
    nametag: string;
}

class Animal {
    numLegs: number;
}

class Bee extends Animal {
    keeper: BeeKeeper;
}

class Lion extends Animal {
    keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}

createInstance(Lion).keeper.nametag;  // typechecks!
createInstance(Bee).keeper.hasMask;   // typechecks!