泛型是指在定義函數(shù)宁改、接口或類的時(shí)候缕探,不預(yù)先指定具體的類型,而在使用的時(shí)候再指定類型的一種特性还蹲。一般用于解決類爹耗、方法、接口的復(fù)用性谜喊,以及對(duì)不特定數(shù)據(jù)類型的支持(類型校驗(yàn))潭兽。
創(chuàng)建一個(gè)函數(shù),要求函數(shù)的返回值就是函數(shù)的參數(shù)斗遏,參數(shù)類型是任意的山卦。可以通過如下方式實(shí)現(xiàn):
function echo(val: any): any {
return val
}
以上使用any類型有幾個(gè)不足之處:首先any類型ts會(huì)放棄類型檢查最易,失去使用ts的初衷及優(yōu)勢怒坯;其次,既然返回值是any類型藻懒,并不能準(zhǔn)確的定義返回值的類型剔猿。此時(shí)泛型就派上用場了。
1. 泛型函數(shù)
實(shí)現(xiàn)一個(gè)函數(shù)嬉荆,函數(shù)接收兩個(gè)參數(shù)归敬,返回值是參數(shù)1為length,參數(shù)2為元素組成的數(shù)組鄙早。
function createArray<T>(length: number, value: T): Array<T> {
const result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
以上就是一個(gè)泛型函數(shù)汪茧, T
代表任意類型。
泛型函數(shù)調(diào)用方式:
- 傳入所有參數(shù)限番,包括類型參數(shù)
createArray<string>(3, 'x') // ['x', 'x', 'x']
- 利用類型推論舱污,直接傳入?yún)?shù)
createArray(3, 'x')
多個(gè)類型參數(shù)
實(shí)現(xiàn)一個(gè)函數(shù),參數(shù)是長度為2弥虐、元素為任意類型的元組扩灯,返回交換位置后的元組媚赖。
function exchange<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
exchange([7, 'sina']); // ['sina', 7]
通過類型別名定義泛型函數(shù)
type IGetArr<T> = (val: T, length: number) => T[]
const getArr: IGetArr<number> = (val: number, length: number = 3): number[] => {
return Array(length).fill(val)
}
getArr('4', 5)
2. 泛型約束
在泛型函數(shù)內(nèi)部使用泛型變量時(shí),由于我們預(yù)先并不知道它是哪種類型珠插,所以不能隨意操作其屬性和方法(比如:獲取泛型變量的length)惧磺,但 T
不一定包含該屬性,所以編譯器會(huì)報(bào)錯(cuò)捻撑。
function getLength<T>(arg: T): T {
console.log(arg.length) // Property 'length' does not exist on type 'T'.
return arg
}
此時(shí)就需要對(duì)泛型變量進(jìn)行約束磨隘,要求其必須包含length屬性,這種操作我們稱之為泛型約束顾患。
定義接口來描述約束條件番捂,并用 extends
關(guān)鍵字實(shí)現(xiàn)約束。
如下泛型函數(shù)的參數(shù)必須具有l(wèi)ength屬性:
interface restrainLength {
length: number
}
function getLength<T extends restrainLength>(arg: T): T {
console.log(arg.length)
return arg
}
多個(gè)參數(shù)時(shí)也可以在泛型約束中使用類型參數(shù)
多個(gè)參數(shù)的泛型函數(shù)描验,一個(gè)類型參數(shù)被另一類型參數(shù)所約束白嘁。
定義一個(gè)函數(shù), 接受兩個(gè)參數(shù) 第一個(gè)是對(duì)象 obj膘流,第二個(gè)是第一參數(shù)對(duì)象中的key絮缅,返回 obj[key]。
使用 keyof
關(guān)鍵字約束呼股。
function getValue<T, K extends keyof T>(obj: T, key: K) {
return obj[key]
}
const obj = { a: 1, b: 2, c: 3 }
getValue(obj, 'a')
getValue(obj, 'd') // Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.
keyof
索引類型查詢操作符
對(duì)應(yīng)任何類型T,keyof T的結(jié)果為該類型上所有公共屬性名的聯(lián)合:
interface Eg1 {
name: string,
readonly age: number,
}
// T1的類型實(shí)則是 "name" | "age"
type T1 = keyof Eg1
class Eg2 {
private name: string;
public readonly age: number;
protected home: string;
}
// T2實(shí)則被約束為 "age"
// 因?yàn)閚ame和home不是公有屬性耕魄,所以不能被keyof獲取到
type T2 = keyof Eg2
interface Eg1 {
name: string,
readonly age: number,
}
interface Eg2 {
sex: string
}
// T1的類型實(shí)則是 "name" | "age" | { sex: string }
type T1 = keyof Eg1 | Eg2
let a: T1 = "name"; // OK
let b: T1 = "age"; // OK
let c: T1 = { // OK
sex: "男"
}
注意:keyof any
的結(jié)果為 string | number | symbol,因?yàn)閷?duì)象的key常見的類型就是這三種
TypeScript 2.8 作用于交叉類型的keyof被轉(zhuǎn)換成作用于交叉成員的keyof的聯(lián)合彭谁。 換句話說吸奴,keyof (A & B)會(huì)被轉(zhuǎn)換成keyof A | keyof B。 這個(gè)改動(dòng)應(yīng)該能夠解決keyof表達(dá)式推斷不一致的問題缠局。
type A = { a: string };
type B = { b: string };
type T1 = keyof (A & B); // "a" | "b"
type T2<T> = keyof (T & B); // keyof T | "b"
type T3<U> = keyof (A & U); // "a" | keyof U
type T4<T, U> = keyof (T & U); // keyof T | keyof U
type T5 = T2<A>; // "a" | "b"
type T6 = T3<B>; // "a" | "b"
type T7 = T4<A, B>; // "a" | "b"
3. 泛型接口
可以通過接口的方式定義函數(shù)需要遵循的約束和規(guī)范则奥。
interface IFoo {
(name: string): string
}
let getInfo: IFoo = function(name: string): string{
return `the name is ${name}`
}
這種方式不具有普適性,name的類型被限制死了狭园《链Γ可以通過含有泛型的接口定義函數(shù),如下:
interface IFoo {
<T>(length: number, val: T): Array<T>
}
const getArr: IFoo = function<T>(length: number, val: T): Array<T> {
return Array(length).fill(val)
}
console.log(getArr(3, 'sian')) // ["sian", "sian", "sian"]
把泛型參數(shù)提前到接口名上唱矛,就可以知道使用的具體是哪個(gè)泛型類型罚舱,這樣接口里的其他成員也能知道這個(gè)參數(shù)的類型。
interface IFoo<T> {
(length: number, val: T): Array<T>;
}
const getFoo: IFoo<string> = function (
length: number,
val: string
): [string, number] {
return [val, length]
}
function getArr<T>(length: number, val: T): Array<T> {
return Array(length).fill(val)
}
const getFoo: IFoo<string> = getArr
getFoo(3, 'sina') // ['sina', 'sina', 'sina']
注意: 此時(shí)使用泛型接口時(shí)绎谦, 需要定義泛型的類型
4. 泛型類
與泛型接口類似管闷,泛型也可以用于類的類型定義中,泛型類使用 <>
括起泛型類型窃肠,跟在類名后面:
class Test<T> {
foo: T
constructor(foo: T) {
this.foo = foo
}
echo(val: T): T {
return val
}
}
const test = new Test<number>(12)
test.echo(3)
泛型參數(shù)的默認(rèn)類型
可以為泛型中的類型參數(shù)指定默認(rèn)類型包个。當(dāng)使用泛型時(shí)沒有在代碼中直接指定類型參數(shù),從實(shí)際值參數(shù)中也無法推測出時(shí)冤留,這個(gè)默認(rèn)類型就會(huì)起作用赃蛛。
function createArray<T = string>(length: number, value: T): Array<T> {
const result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray<number>(3, 3)
在.tsx中使用尾隨逗號(hào)
只有一個(gè)泛型參數(shù)時(shí)恃锉,尾隨逗號(hào)是必須的。否則會(huì)出現(xiàn)編譯錯(cuò)誤呕臂。
const returnInArray = <T,>(value: T): T[] => {
return [value];
};
const strArray = returnInArray<string>('hello');
const numArray = returnInArray<number>(360);
如果有多種類型,則不必使用尾隨逗號(hào)肪跋,例如:<T, Y>
如果使用了尾隨逗號(hào)文件還報(bào)錯(cuò)歧蒋,可以擴(kuò)展一個(gè)空對(duì)象。
const carr = <T extends unknown>(val: T): T[] => {
return [val]
}
carr<string>('3')
carr(4)
T extends unknown
約束不執(zhí)行任何操作州既,并且僅當(dāng)處理.tsx文件時(shí)出現(xiàn)語法錯(cuò)誤時(shí)才需要該約束谜洽。