TypeScript重難點:泛型
這篇文章跟大家分享學(xué)習(xí)ts的又一個重難點「泛型」。在ts中,得泛型者,得天下!
1
什么是泛型
整個ts的學(xué)習(xí),其實就是各種數(shù)據(jù)類型的類型約束的學(xué)習(xí)。當(dāng)我們規(guī)定一個變量只能是number時,它就不能是其他數(shù)據(jù)類型。
let a: number = 20;
a = 'string' // 類型錯誤
在函數(shù)中也是一樣,傳入的參數(shù)類型,與返回的參數(shù)類型,都會被不同的約束規(guī)則約束。
function foo(a: number, b: string): string {
// todo
}
當(dāng)然我們也知道,還可以使用interface,type定義更為復(fù)雜的類型約束。可是這個時候我們就會面臨一個問題。
以我們用的非常多的數(shù)組方法map為例。
[1, 2, 3].map(item => {
return item + 1;
})
我們都知道m(xù)ap方法接收的第一個參數(shù)為一個回調(diào)函數(shù)callback,callback的第一個參數(shù)為數(shù)組的每一項。那么問題就來了,不同的數(shù)組調(diào)用map,數(shù)組的每一項數(shù)據(jù)類型必然不一樣,我們沒辦法簡單的使用某一種數(shù)據(jù)類型來準(zhǔn)確的約束數(shù)組的每一項。
[1, 2, 3].map()
['a', 'b', 'c'].map()
怎么辦?當(dāng)數(shù)組不一樣時?如何來約束呢?
這種情況,需要借助「泛型」來幫助我們。
單一的,明確的類型約束理解起來相對簡單,可是實踐中我們需要對約束稍微放寬一點限制,那么單一的約束就無法滿足需求。泛型,即為更廣泛的約束類型。
仔細(xì)觀察下面的三組案例,思考一下如果我們要自己描述Array類型與數(shù)組中的map方法應(yīng)該怎么做?
interface Person {
name: string,
age: number
}
const demo1: number[] = [1, 2, 3];
const demo2: string[] = ['a', 'b', 'c'];
const demo3: Person[] = [{ name: 'alex', age: 20 }, { name: 'john', age: 10 }, { name: 'hx', age: 21 }];
demo1.map((item) => item);
demo2.map((item) => item);
demo3.map((item) => item);

從圖中可以看出,當(dāng)不同的數(shù)組調(diào)用map時,回調(diào)函數(shù)的參數(shù)item,會自動推導(dǎo)為對應(yīng)的數(shù)據(jù)類型。也就是說,這里的item,必然是使用了泛型進行了更為寬松的約束。具體如下:
interface Array {
map(callbackfn: (value: T, index: number, array: T[]) => U): U[]
}
我們在聲明數(shù)組類型時,定義了一個泛型變量T。T作為泛型變量的含義為:我們在定義約束條件時,暫時還不知道數(shù)組的每一項數(shù)據(jù)類型到底是什么,因此我們只能放一個占位標(biāo)識在這里,待具體使用時再來明確每一項的具體類型。
因此針對數(shù)據(jù)的描述,我們通常可以這樣做:
const arr1: Array = [1, 2, 3];
const arr2: Array = ['a', 'b', 'c'];
const arr3: Array = [{ name: 'alex', age: 20 }, { name: 'john', age: 10 }, { name: 'hx', age: 21 }];
這里分別定義了三個數(shù)組,在約束這些數(shù)組時,我們明確了泛型變量T的具體數(shù)據(jù)類型,分別對應(yīng)為number, string, Person。
那么在描述map時的寫法就很好理解了。回調(diào)函數(shù)callbackfn的第一個參數(shù)就是數(shù)組的每一項,正好就是定義數(shù)組時傳入的泛型變量T,不過回調(diào)函數(shù)會返回一個新的數(shù)組項,因此我們需要重新定義一個新的泛型變量來表達這個新數(shù)組,即為U。
map(callbackfn: (value: T, index: number, array: T[]) => U): U[]
于是我們就使用泛型,準(zhǔn)確的描述了map方法的含義。
如果經(jīng)過上述的解釋還不清楚泛型概念的話,留言
2
基礎(chǔ)語法
如果完整的理解了泛型的概念,那么泛型的基礎(chǔ)知識就比較簡單了,過一遍就OK。
「函數(shù)中使用泛型」
// 聲明一個泛型變量
function identity<T> {}
// 在參數(shù)中使用泛型變量
function identity<T>(arg: T) {}
// 在返回值中使用泛型變量
function identity<T>(arg: T): T {}
// 變量聲明函數(shù)的寫法
let myIdentity: <T>(arg: T) => T = identity;
「接口中使用泛型」
// 使用接口約束一部分?jǐn)?shù)據(jù)類型,使用泛型變量讓剩余部分變得靈活
interface Parseer {
success: boolean,
result: T,
code: number,
desc: string
}
// 接口泛型與函數(shù)泛型結(jié)合
interface Array {
map(callbackfn: (value: T, index: number, array: T[]) => U): U[]
}
「class中使用泛型」
// 注意總結(jié)相似性
declare namespace demo02 {
class GenericNumber {
private value: T;
public add: (x: T, y: T) => T
}
}
// 多個泛型變量傳入
declare namespace demo02 {
class Component {
private constructor(props: P);
public state: S;
}
}
3
泛型實踐場景
「描述數(shù)組」
interface Array {
length: number,
toString(): string,
pop(): T | undefined,
// 注意此處的含義
push(...items: T[]): number,
concat(...items: T[]): T[],
join(separator?: string): string,
reverse(): T[],
shift(): T | undefined;
slice(start?: number, end?: number): T[],
sort(compareFn?: (a: T, b: T) => number): this,
splice(start: number, deleteCount?: number): T[],
// 注意此處的重載寫法
splice(start: number, deleteCount: number, ...items: T[]): T[],
unshift(...items: T[]): number,
indexOf(searchElement: T, fromIndex?: number): number,
lastIndexOf(searchElement: T, fromIndex?: number): number,
every(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean,
some(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean,
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void,
map(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[],
filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[],
filter(callbackfn: (value: T, index: number, array: T[]) => any, thisArg?: any): T[],
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T,
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T,
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U,
// reduceRight 略
// 索引調(diào)用
[n: number]: T,
}
列舉了幾乎所有的數(shù)組方法與特性,如果能夠從上訴描述文件中掌握如何使用數(shù)組方法,那么就表示對于函數(shù),接口,泛型的理解已經(jīng)比較到位了。如果還不能讀懂,則多讀幾遍,留言
「描述數(shù)據(jù)返回結(jié)果」
約定所有的接口返回滿足統(tǒng)一的數(shù)據(jù)格式。但是具體的可用的數(shù)據(jù)結(jié)果則因為情況不同,會有不同的場景。因此使用泛型先定義一個基本的結(jié)構(gòu)約束。
interface Result {
success: true,
code: number,
descript: string,
result: T
}
結(jié)合Promise,當(dāng)數(shù)據(jù)返回結(jié)果為number時
Promise本身就需要接受一個泛型變量,因此這里要注意泛型的嵌套使用
function fetchData(): Promise<Result<number>> {
return http.get('/api/demo/number');
}
當(dāng)數(shù)據(jù)返回結(jié)果為普通JSON數(shù)據(jù)時
interface Person {
name: string,
age: number
}
function fetchData(): Promise<Result<Person>> {
return http.get('/api/demo/person');
}
當(dāng)數(shù)據(jù)返回為數(shù)組時
interface Person {
name: string,
age: number
}
function fetchData(): Promise<Result<Person[]>> {
return http.get('/api/demo/persons');
}
當(dāng)返回結(jié)果為分頁對象時
interface Person {
name: string,
age: number
}
interface Page {
current: number,
pageSize: number,
total: number,
data: T[]
}
function fetchData(): Promise<Result<Page<Person>>> {
return http.get('/api/demo/page/person');
}
分頁對象的返回結(jié)果比較復(fù)雜,因此描述清楚需要多層嵌套,如果你理解了分頁對象,那么基本上泛型就沒有什么問題啦!
