鍍金池/ 教程/ HTML/ 介紹
初始化項目結(jié)構(gòu)
聯(lián)合類型
介紹
介紹
介紹
編譯選項
TypeScript 1.6
介紹
介紹
發(fā)展路線圖
介紹
在MSBuild里使用編譯選項
可迭代性
TypeScript 1.3
介紹
介紹
TypeScript 1.1
變量聲明
即將到來的Angular 2框架是使用TypeScript開發(fā)的。 因此Angular和TypeScript一起使用非常簡單方便
tsconfig.json
介紹
介紹
介紹
在MSBuild里使用編譯選項
使用TypeScript的每日構(gòu)建版本
新建工程
枚舉
三斜線指令
結(jié)合ASP.NET v5使用TypeScript
TypeScript里的this
介紹
TypeScript 1.4
編碼規(guī)范
介紹
模塊解析
ASP.NET 4
架構(gòu)概述
介紹
介紹
ASP.NET Core
TypeScript 1.8
介紹
介紹
創(chuàng)建簡單工程
TypeScript 1.7
TypeScript 1.5
NPM包的類型
支持TypeScript的編輯器

介紹

軟件工程中,我們不僅要創(chuàng)建一致的定義良好的API,同時也要考慮可重用性。 組件不僅能夠支持當前的數(shù)據(jù)類型,同時也能支持未來的數(shù)據(jù)類型,這在創(chuàng)建大型系統(tǒng)時為你提供了十分靈活的功能。

在像C#和Java這樣的語言中,可以使用泛型來創(chuàng)建可重用的組件,一個組件可以支持多種類型的數(shù)據(jù)。 這樣用戶就可以以自己的數(shù)據(jù)類型來使用組件。

泛型之Hello World

下面來創(chuàng)建第一個使用泛型的例子:identity函數(shù)。 這個函數(shù)會返回任何傳入它的值。 你可以把這個函數(shù)當成是echo命令。

不用泛型的話,這個函數(shù)可能是下面這樣:

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

或者,我們使用any類型來定義函數(shù):

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

雖然使用any類型后這個函數(shù)已經(jīng)能接收任何類型的arg參數(shù),但是卻丟失了一些信息:傳入的類型與返回的類型應(yīng)該是相同的。 如果我們傳入一個數(shù)字,我們只知道任何類型的值都有可能被返回。

因此,我們需要一種方法使用返回值的類型與傳入?yún)?shù)的類型是相同的。 這里,我們使用了類型變量,它是一種特殊的變量,只用于表示類型而不是值。

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

我們給identity添加了類型變量T。 T幫助我們捕獲用戶傳入的類型(比如:number),之后我們就可以使用這個類型。 之后我們再次使用了T當做返回值類型?,F(xiàn)在我們可以知道參數(shù)類型與返回值類型是相同的了。 這允許我們跟蹤函數(shù)里使用的類型的信息。

我們把這個版本的identity函數(shù)叫做泛型,因為它可以適用于多個類型。 不同于使用any,它不會丟失信息,像第一個例子那像保持準確性,傳入數(shù)值類型并返回數(shù)值類型。

我們定義了泛型函數(shù)后,可以用兩種方法使用。 第一種是,傳入所有的參數(shù),包含類型參數(shù):

let output = identity<string>("myString");  // type of output will be 'string'

這里我們明確的指定了T是字符串類型,并做為一個參數(shù)傳給函數(shù),使用了<>括起來而不是()。

第二種方法更普遍。利用了類型推論,編譯器會根據(jù)傳入的參數(shù)自動地幫助我們確定T的類型:

let output = identity("myString");  // type of output will be 'string'

注意我們并沒用<>明確的指定類型,編譯器看到了myString,把T設(shè)置為此類型。 類型推論幫助我們保持代碼精簡和高可讀性。如果編譯器不能夠自動地推斷出類型的話,只能像上面那樣明確的傳入T的類型,在一些復(fù)雜的情況下,這是可能出現(xiàn)的。

使用泛型變量

使用泛型創(chuàng)建像identity這樣的泛型函數(shù)時,編譯器要求你在函數(shù)體必須正確的使用這個通用的類型。 換句話說,你必須把這些參數(shù)當做是任意或所有類型。

看下之前identity例子:

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

如果我們想同時打印出arg的長度。 我們很可能會這樣做:

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

如果這么做,編譯器會報錯說我們使用了arg.length屬性,但是沒有地方指明arg具有這個屬性。 記住,這些類型變量代表的是任意類型,所以使用這個函數(shù)的人可能傳入的是個數(shù)字,而數(shù)字是沒有.length屬性的。

現(xiàn)在假設(shè)我們想操作T類型的數(shù)組而不直接是T。由于我們操作的是數(shù)組,所以.length屬性是應(yīng)該存在的。 我們可以像創(chuàng)建其它數(shù)組一樣創(chuàng)建這個數(shù)組:

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

你可以這樣理解loggingIdentity的類型:泛型函數(shù)loggingIdentity,接收類型參數(shù)T,和函數(shù)arg,它是個元素類型是T的數(shù)組,并返回元素類型是T的數(shù)組。 如果我們傳入數(shù)字數(shù)組,將返回一個數(shù)字數(shù)組,因為此時T的的類型為number。 這可以讓我們把泛型變量T當做類型的一部分使用,而不是整個類型,增加了靈活性。

我們也可以這樣實現(xiàn)上面的例子:

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

使用過其它語言的話,你可能對這種語法已經(jīng)很熟悉了。 在下一節(jié),會介紹如何創(chuàng)建自定義泛型像Array<T>一樣。

泛型類型

上一節(jié),我們創(chuàng)建了identity通用函數(shù),可以適用于不同的類型。 在這節(jié),我們研究一下函數(shù)本身的類型,以及如何創(chuàng)建泛型接口。

泛型函數(shù)的類型與非泛型函數(shù)的類型沒什么不同,只是有一個類型參數(shù)在最前面,像函數(shù)聲明一樣:

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

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

我們也可以使用不同的泛型參數(shù)名,只要在數(shù)量上和使用方式上能對應(yīng)上就可以。

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

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

我們還可以使用帶有調(diào)用簽名的對象字面量來定義泛型函數(shù):

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

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

這引導(dǎo)我們?nèi)懙谝粋€泛型接口了。 我們把上面例子里的對象字面量拿出來做為一個接口:

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

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

let myIdentity: GenericIdentityFn = identity;

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

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

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

let myIdentity: GenericIdentityFn<number> = identity;

注意,我們的示例做了少許改動。 不再描述泛型函數(shù),而是把非泛型函數(shù)簽名作為泛型類型一部分。 當我們使用GenericIdentityFn的時候,還得傳入一個類型參數(shù)來指定泛型類型(這里是:number),鎖定了之后代碼里使用的類型。 對于描述哪部分類型屬于泛型部分來說,理解何時把參數(shù)放在調(diào)用簽名里和何時放在接口上是很有幫助的。

除了泛型接口,我們還可以創(chuàng)建泛型類。 注意,無法創(chuàng)建泛型枚舉和泛型命名空間。

泛型類

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

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類的使用是十分直觀的,并且你可能已經(jīng)注意到了,沒有什么去限制它只能使用number類型。 也可以使用字符串或其它更復(fù)雜的類型。

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

alert(stringNumeric.add(stringNumeric.zeroValue, "test"));

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

我們在那節(jié)說過,類有兩部分:靜態(tài)部分和實例部分。 泛型類指的是實例部分的類型,所以類的靜態(tài)屬性不能使用這個泛型類型。

泛型約束

你應(yīng)該會記得之前的一個例子,我們有時候想操作某類型的一組值,并且我們知道這組值具有什么樣的屬性。 在loggingIdentity例子中,我們想訪問arglength屬性,但是編譯器并不能證明每種類型都有length屬性,所以就報錯了。

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

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

為此,我們定義一個接口來描述約束條件。 創(chuàng)建一個包含.length屬性的接口,使用這個接口和extends關(guān)鍵字還實現(xiàn)約束:

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;
}

現(xiàn)在這個泛型函數(shù)被定義了約束,因此它不再是適用于任意類型:

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

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

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

在泛型約束中使用類型參數(shù)

你可以聲明一個類型參數(shù),且它被另一個類型參數(shù)所約束。比如,

function find<T, U extends Findable<T>>(n: T, s: U) {
  // ...
}
find (giraffe, myAnimals);

在泛型里使用類類型

在TypeScript使用泛型創(chuàng)建工廠函數(shù)時,需要引用構(gòu)造函數(shù)的類類型。比如,

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

一個更高級的例子,使用原型屬性推斷并約束構(gòu)造函數(shù)與類實例的關(guān)系。

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 findKeeper<A extends Animal, K> (a: {new(): A;
    prototype: {keeper: K}}): K {

    return a.prototype.keeper;
}

findKeeper(Lion).nametag;  // typechecks!