什么是泛型
理解:泛型就是在編譯期間不確定類型(廣泛之意思),在調(diào)用時(shí)丰辣,由程序員指定的泛型具體指向什么類型撒强。泛型在傳統(tǒng)面向?qū)ο缶幊陶Z言中是極為常見的,ts中當(dāng)然也執(zhí)行泛型笙什。
通俗理解:泛型就是解決 類 接口 方法的重用性飘哨、以及對(duì)不特定數(shù)據(jù)類型的支持
泛型的好處
1. 函數(shù)和類可以輕松的支持多種類型,增強(qiáng)程序的擴(kuò)展性
2. 不必寫多條函數(shù)重載琐凭,冗長(zhǎng)的聯(lián)合類型聲明芽隆,增強(qiáng)代碼的可讀性
3. 靈活控制類型之間的約束
泛型初識(shí)
需求:有個(gè)函數(shù)會(huì)返回任何傳入它的值。
不用泛型的話淘正,這個(gè)函數(shù)可能是下面這樣:
function getData(value: string): string{
return value;//傳入什么返回什么
}
但是這樣摆马,這個(gè)函數(shù)我只能傳字符串類型的參數(shù)給這個(gè)函數(shù),如果想傳數(shù)字類型鸿吆,布爾類型呢囤采。這個(gè)時(shí)候可以使用 any來實(shí)現(xiàn)
function getData(value: any): any{
return value;//傳入什么返回什么
}
any 的缺點(diǎn):
如果想做到傳入什么類型就返回什么類型蕉毯,例如傳入number就返回number,這時(shí)候就可以使用泛型
function getData<T>(value:T):T{
return value;//傳入什么返回什么
}
第一種是思犁,傳入所有的參數(shù)代虾,包含類型參數(shù):
getData<number>(123);
getData<string>("123");
第二種方法更普遍。利用了類型推論 – 即編譯器會(huì)根據(jù)傳入的參數(shù)自動(dòng)地幫助我們確定T的類型:
getData(123);
getData("123");
泛型類型
一激蹲、泛型函數(shù)
泛型函數(shù)的類型與非泛型函數(shù)的類型沒什么不同棉磨,只是有一個(gè)類型參數(shù)在最前面,像函數(shù)聲明一樣:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
我們也可以使用不同的泛型參數(shù)名学辱,只要在數(shù)量上和使用方式上能對(duì)應(yīng)上就可以乘瓤。
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <U>(arg: U) => U = identity;
我們還可以使用帶有調(diào)用簽名的對(duì)象字面量來定義泛型函數(shù):
function identity<T>(arg: T): T {
return arg;
}
對(duì)象字面量的方式來定義泛型類型;
let myIdentity: {<T>(arg: T): T} = identity;
二环形、泛型接口
這引導(dǎo)我們?nèi)懙谝粋€(gè)泛型接口了。 我們把上面例子里的對(duì)象字面量拿出來做為一個(gè)接口:
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
1衙傀、函數(shù)泛型的注解方式;
let myIdentity: <U>(arg: U) => U = identity;
2抬吟、對(duì)象字面量的方式來定義泛型類型;
let myIdentity: {<T>(arg: T): T} = identity;
3、接口泛型的注解方式;
let myIdentity: GenericIdentityFn = identity;
想把泛型參數(shù)當(dāng)作整個(gè)接口的一個(gè)參數(shù)统抬。
這樣我們就能清楚的知道使用的具體是哪個(gè)泛型類型
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
例子:
interface GenericIdentityFn{
(value1:string, value2:string):string;
}
function identity(value1:string, value2:string):string{
return value1 + value2;
}
let myIdentity: GenericIdentityFn = identity
myIdentity("name", "張三")
泛型實(shí)現(xiàn)上面函數(shù)
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
三火本、泛型類
泛型類看上去與泛型接口差不多。 泛型類使用( <> )括起泛型類型聪建,跟在類名后面钙畔。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
使用 number 類型
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) { return x + y; };
console.log(myGenericNumber.add(1, 2))
也可以使用字符串或其它更復(fù)雜的類型。
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "第一次 ";
stringNumeric.add = function (x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "testW逼H婿盅弛!"));
示例:
普通類
class MinClass {
public list: number[] = [];
add(num: number) {
this.list.push(num);
}
min(): number {
var minNum = this.list[0];
for (var i = 0; i < this.list.length; i++) {
if (minNum > this.list[i]) {
minNum = this.list[i];
}
}
console.log(this.list);
return minNum;
}
}
// 調(diào)用
var m = new MinClass();
m.add(3);
m.add(2);
m.add(4);
console.log(m.min());// 2
上邊的只能傳入數(shù)字類型钱骂,使用泛型實(shí)現(xiàn)上面的 類
class MinClass<T>{
public list: T[] = [];
add(num: T): void {
this.list.push(num);
}
min(): T {
var minNum = this.list[0];
for (var i = 0; i < this.list.length; i++) {
if (minNum > this.list[i]) {
minNum = this.list[i];
}
}
return minNum;
}
}
// 調(diào)用,實(shí)例化時(shí)候要先聲明參數(shù)類型<bumber
var m = new MinClass<number>();
m.add(2);
m.add(4);
m.add(3);
console.log(m.min());// 2
四挪鹏、約束泛型
作某類型的一組值见秽,并且我們知道這組值具有什么樣的屬性。這個(gè)時(shí)候我們想訪問arg的length屬性時(shí)讨盒,例如:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length) // Property 'length' does not exist on type 'T'.
return arg
}
想訪問arg的length屬性解取,但是編譯器并不能證明每種類型都有l(wèi)ength屬性(比如說 number 類型、boolean 類型)返顺,所以就報(bào)錯(cuò)了禀苦。
這種情況可以使用泛型約束了
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length)
return arg
}
const arr = loggingIdentity([1, 2, 3])
上面代碼中約束了傳入的參數(shù)需要是任意類型的數(shù)組,但 Object遂鹊,String類型都是有l(wèi)ength屬性的振乏,這時(shí)候就不滿足這種場(chǎng)景了。這時(shí)候需要對(duì)泛型進(jìn)行約束秉扑,允許這個(gè)函數(shù)傳入包含length屬性的變量(約束泛型):
interface Lengthwise {
length: number
}
// type Lengthwise = {
// length: number
// }
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length)
return arg
}
const len01 = loggingIdentity('abc') // 3
const len02 = loggingIdentity({ length: 12 }) // 12
const len03 = loggingIdentity([1, 2, 3]) // 3
上面代碼中慧邮,定義了一個(gè)接口 Lengthwise ,在函數(shù) loggingIdentity 的泛型中使用 extends 關(guān)鍵字繼承了Lengthwise 接口舟陆,這樣調(diào)用函數(shù) loggingIdentity 時(shí)傳的參數(shù)必須要有 length 這個(gè)屬性
索引類型(keyof)
官方定義:keyof該操作符可以用于獲取某種類型的所有鍵误澳,其返回類型是聯(lián)合類型(keyof的應(yīng)用多第三方庫(kù)的源碼中常見)
聲明一個(gè)類型參數(shù),且它被另一個(gè)類型參數(shù)所約束秦躯。 比如忆谓,現(xiàn)在我們想要用屬性名從對(duì)象里獲取這個(gè)屬性。 并且我們想要確保這個(gè)屬性存在于對(duì)象obj上踱承,因此我們需要在這兩個(gè)類型之間使用約束倡缠。
function getProperty<T, K extends keyof T>(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'.
js中的Object.keys大家肯定都用過, 獲取對(duì)象的鍵值, ts中的keyof和他類似, 可以用來獲取對(duì)象類型的鍵值:
type A = keyof {a:1, b:'123'} // 'a'|'b'
type B = keyof [1, 2] // '1'|'2'|'push'... 獲取到內(nèi)容的同時(shí), 還得到了Array原型上的方法和屬性(實(shí)戰(zhàn)中暫時(shí)沒遇到這種需求, 了解即可)
可以獲得鍵值, 也可以獲取對(duì)象類型的值的類型:
type C = A['a'] // 等于type C = 1;
let c:C = 2 // 錯(cuò)誤, 值只能是1
多重約束&交叉類型
示例1
交叉類型是將多個(gè)類型合并為一個(gè)類型米母。 這讓我們可以把現(xiàn)有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性毡琉。 例如铁瞒,Person & Serializable & Loggable
同時(shí)是Person
和Serializable
和Loggable
。 就是說這個(gè)類型的對(duì)象同時(shí)擁有了這三種類型的成員桅滋。
我們大多是在混入(mixins)或其它不適合典型面向?qū)ο竽P偷牡胤娇吹浇徊骖愋偷氖褂谩?(在JavaScript里發(fā)生這種情況的場(chǎng)合很多;鬯!) 下面是如何創(chuàng)建混入的一個(gè)簡(jiǎn)單例子:
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
class Person {
constructor(public name: string) { }
}
interface Loggable {
log(): void;
}
class ConsoleLogger implements Loggable {
log() {
// ...
}
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();
示例2
interface Sentence {
content: string;
title: string;
}
interface Music {
url: string;
}
class Test<T extends Sentence & Music>{
props: T
constructor(public arg: T) {
this.props = arg
}
info() {
return {
url: this.props.url,
content: this.props.content,
title: this.props.title
}
}
}
let a = new Test({
title: "泛型",
content: "xasdjoasdoijasiodhoia",
url: "aaaaaaaaaaaa"
})
console.log(a);
在泛型里使用類類型
在TypeScript使用泛型創(chuàng)建工廠函數(shù)時(shí),需要引用構(gòu)造函數(shù)的類類型丐谋。比如芍碧,
1、參數(shù)c的注解是一個(gè)對(duì)象号俐,可以把它看成一個(gè)接口泌豆;
2、對(duì)象里面的屬性是一個(gè)方法( new() )吏饿,方法的返回值是 (T)
可以寫成 function create<T>(c: new() => T): T { return new c(); }
3踪危、有一個(gè) new 方法說明這個(gè)方法是個(gè)構(gòu)造方法,那么 T 就代表構(gòu)造方法的實(shí)例
4猪落、返回的是實(shí)例 T贞远,所以需要調(diào)用 new方法來獲取實(shí)例
function create<T>(c: {new(): T; }): T {
return new c();
}
不要亂用泛型
泛型主要是為了約束, 或者說縮小類型范圍, 如果不能約束功能, 就代表不需要用泛型:
function convert<T>(input:T[]):number{
return input.length;
}
這樣用泛型就沒有什么意義了, 和any類型沒有什么區(qū)別.
TypeScript 中提供了許多常用的泛型工具類型,這些工具類型可以幫助我們更輕松地定義通用的類型映射和轉(zhuǎn)換笨忌。以下是一些常見的 TypeScript 泛型工具類型:
Partial<T>:將類型 T 的所有屬性設(shè)置為可選蓝仲。
Required<T>:將類型 T 的所有屬性設(shè)置為必選。
Readonly<T>:將類型 T 的所有屬性設(shè)置為只讀官疲。
Record<K, T>:創(chuàng)建一個(gè)具有指定鍵類型 K 和值類型 T 的對(duì)象類型袱结。
Pick<T, K>:從類型 T 中選擇部分屬性,僅包含鍵為 K 的屬性途凫。
Omit<T, K>:從類型 T 中排除某些屬性垢夹,即移除鍵為 K 的屬性。
Exclude<T, U>:從類型 T 中排除可以賦值給 U 的類型颖榜。
Extract<T, U>:從類型 T 中提取可以賦值給 U 的類型棚饵。
NonNullable<T>:從類型 T 中剔除 null 和 undefined 類型。
ReturnType<T>:獲取函數(shù)類型 T 的返回類型掩完。
Parameters<T>:獲取函數(shù)類型 T 的參數(shù)類型組成的元組類型噪漾。