前言
關(guān)于概念,本文不會(huì)過多敘述。
先來看個(gè)例子,體會(huì)一下泛型解決的問題吧。
我們定義一個(gè) print
函數(shù),這個(gè)函數(shù)的功能是把傳入的參數(shù)打印出來,最后再返回這個(gè)參數(shù),傳入?yún)?shù)的類型是 string
,函數(shù)返回類型為 string
。
function print(arg: string): string {
console.log(arg)
return arg
}
假如現(xiàn)在需求變了,我還需要打印 number
類型,請(qǐng)問怎么辦?可使用聯(lián)合類型來改造!
function print(arg:string | number):string | number {
console.log(arg)
return arg
}
現(xiàn)在需求又變了,我還需要打印 string
數(shù)組、number
數(shù)組,甚至任何類型,怎么辦?直接 any
!
function print(arg:any):any {
console.log(arg)
return arg
}
需要注意的是寫 any
類型不好,畢竟在 TS
中盡量不要寫 any
。
而且這也不是我們想要的結(jié)果,只能說傳入的值是 any
類型,輸出的值也是 any
類型,傳入和返回并不是統(tǒng)一的。
這么寫甚至還會(huì)出現(xiàn) bug
。
const res: string = print(123)
定義 string
類型來接收 print
函數(shù)的返回值,返回的是個(gè) number
類型,TS
并不會(huì)報(bào)錯(cuò)提示我們。
這個(gè)時(shí)候,泛型就出現(xiàn)了,它可以輕松解決輸入輸出要一致的問題。
另外,泛型不是為了解決這一個(gè)問題設(shè)計(jì)出來的,泛型還解決了很多其他問題,這里是通過這個(gè)例子來引出泛型。
基本使用
泛型的語(yǔ)法是
<>
里寫類型參數(shù),一般可以用T
來表示。
一、處理函數(shù)參數(shù)
我們使用泛型來解決前面的問題,如下代碼所示:
function print<T>(arg:T):T {
console.log(arg)
return arg
}
這樣,我們就做到了輸入和輸出的類型統(tǒng)一,且可以輸入輸出任何類型。
如果類型不統(tǒng)一,就會(huì)報(bào)錯(cuò):
泛型中的 T
就像一個(gè)占位符、或者說一個(gè)變量,在使用的時(shí)候可以把定義的類型 像參數(shù)一樣傳入,它可以 原封不動(dòng)地輸出。
泛型的寫法對(duì)前端工程師來說是有些古怪,比如 <> T
,但記住就好,只要一看到 <>
,就知道這是泛型。
我們?cè)谑褂玫臅r(shí)候可以有兩種方式指定類型:
- 定義要使用的類型
- TS 類型推斷,自動(dòng)推導(dǎo)出類型
print<string>('hello') // 定義 T 為 string
print('hello') // TS 類型推斷,自動(dòng)推導(dǎo)類型為 string
我們知道,type
和 interface
都可以定義函數(shù)類型,也用泛型來寫一下,type
這么寫:
type Print = <T>(arg: T) => T
const printFn:Print = function print(arg) {
console.log(arg)
return arg
}
interface
這么寫:
interface Iprint<T> {
(arg: T): T
}
function print<T>(arg:T) {
console.log(arg)
return arg
}
const myPrint: Iprint<number> = print
二、默認(rèn)參數(shù)
如果要給泛型加默認(rèn)參數(shù),可以這么寫:
interface Iprint<T = number> {
(arg: T): T
}
function print<T>(arg:T) {
console.log(arg)
return arg
}
const myPrint: Iprint = print
這樣默認(rèn)就是 number
類型了,怎么樣,是不是感覺 T
就如同函數(shù)參數(shù)一樣呢?
三、處理多個(gè)函數(shù)參數(shù)
現(xiàn)在有這么一個(gè)函數(shù),傳入一個(gè)只有兩項(xiàng)的元組,交換元組的第 0 項(xiàng)和第 1 項(xiàng),返回這個(gè)元組。
function swap(tuple) {
return [tuple[1], tuple[0]]
}
這么寫,我們就喪失了類型,用泛型來改造一下。
我們用 T 代表第 0 項(xiàng)的類型,用 U 代表第 1 項(xiàng)的類型。
function swap<T, U>(tuple: [T, U]): [U, T]{
return [tuple[1], tuple[0]]
}
這樣就可以實(shí)現(xiàn)了元組第 0 項(xiàng)和第 1 項(xiàng)類型的控制。
傳入的參數(shù)里,第 0 項(xiàng)為 string 類型,第 1 項(xiàng)為 number 類型。
在交換函數(shù)的返回值里,第 0 項(xiàng)為 number 類型,第 1 項(xiàng)為 string 類型。
第 0 項(xiàng)上全是 number 的方法。
第 1 項(xiàng)上全是 string 的方法。
四、函數(shù)副作用操作
泛型不僅可以很方便地約束函數(shù)的參數(shù)類型,還可以用在函數(shù)執(zhí)行副作用操作的時(shí)候。
比如我們有一個(gè)通用的異步請(qǐng)求方法,想根據(jù)不同的 url 請(qǐng)求返回不同類型的數(shù)據(jù)。
function request(url:string) {
return fetch(url).then(res => res.json())
}
調(diào)一個(gè)獲取用戶信息的接口:
request('user/info').then(res =>{
console.log(res)
})
這時(shí)候的返回結(jié)果 res 就是一個(gè) any 類型,非常討厭。
我們希望調(diào)用 API 都 清晰的知道返回類型是什么數(shù)據(jù)結(jié)構(gòu),就可以這么做:
interface UserInfo {
name: string
age: number
}
function request<T>(url:string): Promise<T> {
return fetch(url).then(res => res.json())
}
request<UserInfo>('user/info').then(res =>{
console.log(res)
})
這樣就能很舒服地拿到接口返回的數(shù)據(jù)類型,開發(fā)效率大大提高:
約束泛型
假設(shè)現(xiàn)在有這么一個(gè)函數(shù),打印傳入?yún)?shù)的長(zhǎng)度,我們這么寫:
function printLength<T>(arg: T): T {
console.log(arg.length)
return arg
}
因?yàn)椴淮_定 T 是否有 length 屬性,會(huì)報(bào)錯(cuò):
那么現(xiàn)在我想約束這個(gè)泛型,一定要有 length 屬性,怎么辦?
可以和 interface 結(jié)合,來約束類型。
interface ILength {
length: number
}
function printLength<T extends ILength>(arg: T): T {
console.log(arg.length)
return arg
}
這其中的關(guān)鍵就是 <T extends ILength>
,讓這個(gè)泛型繼承接口 ILength,這樣就能約束泛型。
我們定義的變量一定要有 length 屬性,比如下面的 str、arr 和 obj,才可以通過 TS 編譯。
const str = printLength('lin')
const arr = printLength([1,2,3])
const obj = printLength({ length: 10 })
這個(gè)例子也再次印證了 interface 的 duck typing。
只要你有 length 屬性,都符合約束,那就不管你是 str,arr 還是obj,都沒問題。
當(dāng)然,我們定義一個(gè)不包含 length 屬性的變量,比如數(shù)字,就會(huì)報(bào)錯(cuò):
泛型的一些應(yīng)用
使用泛型,可以在定義函數(shù)、接口或類的時(shí)候,不預(yù)先指定具體類型,而是在使用的時(shí)候再指定類型。
一、泛型約束類
定義一個(gè)棧,有入棧和出棧兩個(gè)方法,如果想入棧和出棧的元素類型統(tǒng)一,就可以這么寫:
class Stack<T> {
private data: T[] = []
push(item:T) {
return this.data.push(item)
}
pop():T | undefined {
return this.data.pop()
}
}
在定義實(shí)例的時(shí)候?qū)戭愋?,比如,入棧和出棧都要?number 類型,就這么寫:
const s1 = new Stack<number>()
這樣,入棧一個(gè)字符串就會(huì)報(bào)錯(cuò):
這是非常靈活的,如果需求變了,入棧和出棧都要是 string 類型,在定義實(shí)例的時(shí)候改一下就好了:
const s1 = new Stack<string>()
這樣,入棧一個(gè)數(shù)字就會(huì)報(bào)錯(cuò):
特別注意的是,泛型無(wú)法約束類的靜態(tài)成員。
給 pop 方法定義 static 關(guān)鍵字,就報(bào)錯(cuò)了
二、泛型約束接口
使用泛型,也可以對(duì) interface 進(jìn)行改造,讓 interface 更靈活。
interface IKeyValue<T, U> {
key: T
value: U
}
const k1:IKeyValue<number, string> = { key: 18, value: 'lin'}
const k2:IKeyValue<string, number> = { key: 'lin', value: 18}
三、泛型定義數(shù)組
定義一個(gè)數(shù)組,我們之前是這么寫的:
const arr: number[] = [1,2,3]
現(xiàn)在這么寫也可以:
const arr: Array<number> = [1,2,3]
數(shù)組項(xiàng)寫錯(cuò)類型,報(bào)錯(cuò)
實(shí)戰(zhàn) - 泛型約束后端接口參數(shù)類型
我們來看一個(gè)泛型非常有助于項(xiàng)目開發(fā)的用法,約束后端接口參數(shù)類型。
import axios from 'axios'
interface API {
'/book/detail': {
id: number,
},
'/book/comment': {
id: number
comment: string
}
...
}
function request<T extends keyof API>(url: T, obj: API[T]) {
return axios.post(url, obj)
}
request('/book/comment', {
id: 1,
comment: '非常棒!'
})
這樣在調(diào)用接口的時(shí)候就會(huì)有提醒,比如:
路徑寫錯(cuò)了:
參數(shù)類型傳錯(cuò)了:
參數(shù)傳少了:
寫在后面
泛型(Generics),從字面上理解,泛型就是一般的,廣泛的。
泛型是指在定義函數(shù)、接口或類的時(shí)候,不預(yù)先指定具體類型,而是在使用的時(shí)候再指定類型。
泛型中的 T 就像一個(gè)占位符、或者說一個(gè)變量,在使用的時(shí)候可以把定義的類型像參數(shù)一樣傳入,它可以原封不動(dòng)地輸出。
泛型在成員之間提供有意義的約束,這些成員可以是:函數(shù)參數(shù)、函數(shù)返回值、類的實(shí)例成員、類的方法等。
用一張圖來總結(jié)一下泛型的好處:文章來源:http://www.zghlxwxcb.cn/news/detail-413664.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-413664.html
到了這里,關(guān)于TypeScript - 泛型 Generics(通俗易懂詳細(xì)教程)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!