泛型是程序設計語言中的一種風格或范式,相當於類型模板,允許在聲明類、接口或函數等成員時忽略類型,而在未來使用時再指定類型,其主要目的是為它們提供有意義的約束,提升代碼的可重用性。
一、泛型參數
當一個函數需要能處理多種類型的參數和返回值,並且還得約束它們之間的關系(例如類型要相同)時,就可以采用泛型的語法,如下所示。
function send<T>(data: T): T { return data; }
函數名稱后面跟了<T>,其中把T稱為泛型參數或泛型變量,表示某種數據類型。注意,T只是個占位符,可以命名的更含語義,例如TKey、TValue等。在使用時,既可以指定類型,也可以利用類型推論自動確定類型,如下所示。
send<number>(10); //指定類型 send(10); //類型推論
當需要處理T類型的數組時,可以像下面這么寫。
function send<T>(data: T[]): T[] { return data; } send<number>([1, 2, 3]);
當指定一個泛型函數的類型時,需要包含泛型參數,如下所示,其中泛型參數和函數參數的名稱都可與定義時的不同。
let func: <U>(data: U) => U = send;
泛型參數還支持傳遞多個,只需在聲明時增加類型占位符即可。在下面的示例中,將T和U合並成了一個元組類型,還有許多其它用法,將在后面講解。
function send<T, U>(data: [T, U]): [T, U] { return data; } send<number, string>([1, "a"]);
二、泛型接口
在接口中,可利用泛型來約束函數的結構,如下所示,接口中聲明的調用簽名包含泛型參數。
interface Func { <T>(str: T): T; } function send<T>(str: T): T { return str; } let fn: Func = send;
泛型參數還可以作為接口的一個參數存在,即把用尖括號包裹的泛型參數移到接口名稱之后,如下所示。
interface Func<T> { (str: T): T; } function send<T>(str: T): T { return str; } let fn: Func<string> = send;
當把Func接口作為類型使用時,需要向其傳入一個類型,例如上面賦值語句中的string。
三、泛型類
泛型類與泛型接口類似,也是在名稱后添加泛型參數,如下所示,其中send屬性中的“=>”符號不表示箭頭函數,而是用來定義方法的返回值類型。
class Person<T> { name: T; send: (data: T) => T; }
在實例化泛型類時,需要為其指定一種類型,如下所示。
let person = new Person<string>(); person.send = function(data) { return data; }
注意,類的靜態部分不能使用泛型參數。
四、泛型約束
在使用泛型時,由於事先不清楚參數的數據類型,因此不能隨意調用它的屬性或方法,甚至無法對其使用運算符。在下面的示例中,訪問了data的length屬性,但由於編譯器無法確定它的類型,因此就會報錯。
function send<T>(data: T): T { console.log(data.length); return data; }
TypeScript允許為泛型參數添加約束條件,從而就能調用相應的屬性或方法了,如下所示,通過extends關鍵字約束T必須是string的子類型。
function send<T extends string>(data: T): T { console.log(data.length); return data; }
在添加了這個約束之后,send()函數就無法接收數字類型的參數了,如下所示。
send("10"); //正確 send(10); //錯誤
1)創建類的實例
在使用泛型創建類的工廠函數時,需要聲明T類型擁有構造函數,如下所示。
class Programmer { } function create<T>(ctor: {new(): T}): T { return new ctor(); } create(Programmer);
用“{new(): T}”替代原先的類型占位符,表示可以被new運算符實例化,並且得到的是T類型,另一種相同作用的寫法如下所示。
function create<T>(ctor: new()=>T): T { return new ctor(); }
2)多個泛型參數
在TypeScript中,多個泛型參數之間也可以相互約束,如下所示,創建了基類Person和派生類Programmer,並將create()函數中的T約束為U的子類型。
class Person { } class Programmer extends Person { } function create<T extends U, U>(target: T, source: U): T { return target; }
當傳遞給create()函數的參數不符合約束條件時,就會在編譯階段報錯,如下所示。
create(Programmer, Person); //正確 create(Programmer, 10); //錯誤