一泪蔫、泛型
泛型(Generics)是指在定義函數(shù)、接口或類(lèi)的時(shí)候,不預(yù)先指定具體的類(lèi)型介评,而在使用的時(shí)候再指定類(lèi)型的一種特性。
1.1泛型的基本使用
首先實(shí)現(xiàn)一個(gè)函數(shù) getArr 爬舰,它創(chuàng)建一個(gè)數(shù)組们陆,將傳入的可變數(shù)量參數(shù)push到數(shù)組,并且將這個(gè)數(shù)組返回.
我限制了傳入?yún)?shù)的類(lèi)型string|number
傳入字符串'foo',''bar,并通過(guò)forEach方法獲取item的長(zhǎng)度
type Arr = (...args: (string | number)[]) => Array<string | number>
let getArr: Arr
getArr = (...args) => {
const arr = []
arr.push(...args)
return arr
}
getArr('foo', 'bar').forEach(item => {
item.length // “string | number”上不存在屬性“l(fā)ength”
})
但TS報(bào)錯(cuò)了,雖然邏輯上沒(méi)有錯(cuò)誤,但TypeScript 不確定一個(gè)聯(lián)合類(lèi)型的變量到底是哪個(gè)類(lèi)型的時(shí)候,我們只能訪問(wèn)此聯(lián)合類(lèi)型的所有類(lèi)型中共有的屬性或方法.返回的數(shù)組類(lèi)型是string|number,而number類(lèi)型的數(shù)據(jù)是沒(méi)有l(wèi)ength這個(gè)屬性的
此時(shí)可以使用類(lèi)型斷言情屹,將 item斷言成 string類(lèi)型
type Arr = (...args: (string | number)[]) => Array<string | number>
let getArr: Arr
getArr = (...args) => {
const arr = []
arr.push(...args)
return arr
}
getArr('foo', 'bar').forEach(item => {
(item as string).length
})
類(lèi)型斷言非常好用,但只能夠「欺騙」TypeScript 編譯器坪仇,無(wú)法避免運(yùn)行時(shí)的錯(cuò)誤,反而濫用類(lèi)型斷言可能會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤.
有沒(méi)有其他辦法呢?
試著把返回?cái)?shù)組的類(lèi)型改成any呢
type Arr = (...args: (string | number)[]) => Array<any>
let getArr: Arr
getArr = (...args) => {
const arr = []
arr.push(...args)
return arr
}
getArr('foo', 'bar').forEach(item => {
item.length;// 3
item.toFixed(1) // 正常編譯
})
更改后,可以正確獲取item.length,但是卻丟失了一些信息.item.toFixed()方法也能正常編譯了. 要知道toFixed()只能作用于number類(lèi)型的數(shù)據(jù).這顯然是不對(duì)的.
果然瀏覽器報(bào)錯(cuò)了.
- error Uncaught TypeError: item.toFixed is not a function
這已經(jīng)失去了使用TS的初衷:在書(shū)寫(xiě)代碼時(shí)就發(fā)現(xiàn)錯(cuò)誤
有什么辦法可以讓返回的類(lèi)型與傳入的類(lèi)型保持一致呢
1.1.1 函數(shù)重載
function getArr(...args: string[]): Array<string>
function getArr(...args: number[]): Array<number>
function getArr(...args: (string | number)[]): Array<string | number> {
const arr = []
arr.push(...args)
return arr
}
getArr('foo', 'bar').forEach(item => {
item.length;// 3
item.toFixed(1) // 報(bào)錯(cuò) string上不存在error
})
通過(guò)函數(shù)重載,我指定了輸入輸出類(lèi)型一致.使其能夠正確的訪問(wèn)正確屬性,對(duì)不存在的屬性報(bào)錯(cuò)
但這樣寫(xiě)代碼未免太冗余了.
1.1.2 使用泛型
let getArr = function<T>(...args: T[]): Array<T> {
const arr:T[] = [] //泛型變量可用于函數(shù)體中
arr.push(...args)
return arr
}
getArr<string>('foo', 'bar').forEach(item => {
item.length;// 3
})
getArr<number>('foo', 'bar').forEach(item => {
item.toFixed(1) // 正常編譯
})
在匿名函數(shù)名前添加了 <T>垃你, T 用來(lái)指代任意輸入的類(lèi)型椅文,通過(guò)泛型變量就可以使輸入輸出類(lèi)型保持一致.
在調(diào)用的時(shí)候喂很,可以在函數(shù)名后指定它具體的類(lèi)型為 string。當(dāng)然皆刺,也可以不手動(dòng)指定少辣,而讓類(lèi)型推論自動(dòng)推算.
1.2 多個(gè)泛型參數(shù)
定義泛型的時(shí)候,可以一次定義多個(gè)類(lèi)型參數(shù):
type Func = <T, U>(x: T, y: U) => [U, T]
let swap: Func = (x, y) => {
return [y,x]
}
swap(1,'1'); // ['1',1]
函數(shù)swap返回一個(gè)元組,元組中的數(shù)據(jù)類(lèi)型與函數(shù)傳入?yún)?shù)類(lèi)型一致.
1.3 泛型約束
使用泛型時(shí),不預(yù)先指定具體的類(lèi)型芹橡,而在使用的時(shí)候再指定.
function foo<T>(x: T): T {
return x
}
foo<string>('foo').length
這未免有點(diǎn)與TypeScript優(yōu)點(diǎn)相悖,類(lèi)型限制shape應(yīng)該在定義而不是在使用時(shí).
此時(shí)如果把訪問(wèn)length屬性放在函數(shù)體中,
function foo<T>(x: T): T {
x.length // 類(lèi)型“T”上不存在屬性“l(fā)ength”
return x
}
foo('foo')
報(bào)錯(cuò)了,此時(shí)x的類(lèi)型為any,不是所有的類(lèi)型都有l(wèi)ength這個(gè)屬性,所以不能隨意的操作它的屬性或方法.
那么怎么在定義函數(shù)時(shí)給泛型類(lèi)型加以限制呢.
使用extends 關(guān)鍵字
(1)直接繼承具體類(lèi)型
function foo<T extends string>(x: T): T {
x.length
return x
}
foo('foo')
此時(shí)才真正做到了輸入輸出類(lèi)型一致,并且限制了輸入類(lèi)型的shape
(2)繼承接口
interface Bar {
length: number
}
function foo<T extends Bar>(x: T): T {
x.length
return x
}
foo('foo') // ?
foo({ length: 233 }) // ?
限制了泛型必須要有l(wèi)ength這個(gè)屬性.
(3)多個(gè)泛型參數(shù)之間相互約束
function foo<T, U extends keyof T>(obj: T, attr: U): T {
console.log(obj[attr]);
return obj
}
const bar = {
name: 'bar'
}
foo(bar, 'name')
foo(bar, 'age') // error 類(lèi)型“"age"”的參數(shù)不能賦給類(lèi)型“"name"”的參數(shù)毒坛。
限制了泛型U類(lèi)型必須是T類(lèi)型的key
1.4 泛型默認(rèn)值
function foo<T = number>(x: T): T {
return x
}
在 TypeScript 2.3 以后,我們可以為泛型中的類(lèi)型參數(shù)指定默認(rèn)類(lèi)型林说。當(dāng)使用泛型時(shí)沒(méi)有在代碼中直接指定類(lèi)型參數(shù)煎殷,從實(shí)際值參數(shù)中也無(wú)法推測(cè)出時(shí),這個(gè)默認(rèn)類(lèi)型就會(huì)起作用腿箩。
二豪直、泛型與接口
(1) interface
將1.1.2 使用泛型例子可用接口重構(gòu)
interface Arr {
<T>(...args: T[]): Array<T>
}
let getArr: Arr
getArr = (...args) => {
const arr = []
arr.push(...args)
return arr
}
也可以把泛型參數(shù)提前到接口名上,應(yīng)用于混合類(lèi)型接口
interface Arr<T>{
(...args: T[]) :Array<T>
}
let getArr: Arr<any> //定義類(lèi)型any
getArr = (...args) => {
const arr = []
arr.push(...args)
return arr
}
注意,此時(shí)在使用泛型接口的時(shí)候珠移,需要定義泛型的類(lèi)型弓乙。且會(huì)出現(xiàn)unexpected error .慎用.
(2) type同理
type Arr = <T>(...args: T[]) => Array<T>
let getArr: Arr
getArr = (...args) => {
const arr = []
arr.push(...args)
return arr
}
提取到接口名上也要定義泛型類(lèi)型,但其實(shí)多此一舉
type Arr<T> = (...args: T[]) => Array<T>
let getArr: Arr<any>= (...args) => {
const arr = []
arr.push(...args)
return arr
}
三、泛型與類(lèi)
與泛型接口類(lèi)似钧惧,泛型也可以用于類(lèi)的類(lèi)型定義中.
class Person<T = string> {
name: T
constructor(name: T) {
this.name = name
}
run(): T {
const foo: T = this.name
return foo
}
}
const why = new Person<string>('why')