ypeScript-泛型-1-概念,泛型接口和泛型類
?鬼哥本周將通過幾篇優(yōu)秀的Typescript文章,讓大家學(xué)習(xí)到Typescript一些高級的用法,讓大家對Typescript更加的深入理解,并且更好實(shí)踐到工作當(dāng)中,【一共五篇文章】,關(guān)注我們一起完成這個(gè)系列的學(xué)習(xí)
原文:https://github.com/leslie1943
?
?? 泛型是什么?
?軟件工程中,我們不僅要創(chuàng)建一致的定義良好的 API,同時(shí)也要考慮可重用性. 組件不僅能夠支持當(dāng)前的數(shù)據(jù)類型,同時(shí)也能支持未來的數(shù)據(jù)類型,這在創(chuàng)建大型系統(tǒng)時(shí)為你提供了十分靈活的功能.
?
在像 C# 和 Java 這樣的語言中, 可以使用泛型來創(chuàng)建可重用的組件, 一個(gè)組件可以支持多種類型的數(shù)據(jù). 這樣用戶就可以以自己的數(shù)據(jù)類型來使用組件. 設(shè)計(jì)泛型的關(guān)鍵目的在成員之間提供有意義的約束, 這些成員可以是: 類的實(shí)例成員, 類的方法, 函數(shù)參數(shù) 和 函數(shù)返回值 為了便于大家更好地理解上述的內(nèi)容, 我們來舉個(gè)例子, 在這個(gè)例子中, 我們將一步步揭示泛型的作用.
?首先我們來定義一個(gè)通用的 identity 函數(shù), 該函數(shù)接收一個(gè)參數(shù)并直接返回它:
?
function identity(value) {
return value
}
console.info(identity(1)) // 1
現(xiàn)在, 我們將 identity 函數(shù)做適當(dāng)?shù)恼{(diào)整, 以支持 TypeScript 的 Number 類型的參數(shù):
function identity(value: Number): Number {
return value
}
console.info(identity(1)) // 1
這里 identity 的問題是我們將 Number 類型分配給參數(shù)和返回類型, 使該函數(shù)僅可用于該原始類型. 但該函數(shù)并不是可擴(kuò)展或通用的, 很明顯這并不是我們所希望的. 我們確實(shí)可以把 Number 換成 any, 我們失去了定義應(yīng)該返回哪種類型的能力, 并且在這個(gè)過程中使編譯器失去了類型保護(hù)的作用. 我們的目標(biāo)是讓 identity 函數(shù)可以適用于任何特定的類型, 為了實(shí)現(xiàn)這個(gè)目標(biāo), 我們可以使用泛型來解決這個(gè)問題, 具體實(shí)現(xiàn)方式如下
// ?????? Step - 3
function identity3<T>(value: T): T {
return value
對于剛接觸 TypeScript 泛型的讀者來說, 首次看到 語法會感到陌生. 但這沒什么可擔(dān)心的, 就像傳遞參數(shù)一樣, 我們傳遞了我們想要用于特定函數(shù)調(diào)用的類型.
當(dāng)我們調(diào)用
identity<Number>(1),Number類型就像參數(shù)1一樣, 它將出現(xiàn)在T的任何位置填充該類型. 圖中<T>內(nèi)部的T被稱為類型變量, 它是我們希望傳遞給identity函數(shù)的類型占位符. 同時(shí)它被分配給value參數(shù)用來代替它的類型, 此時(shí)T充當(dāng)?shù)氖穷愋? 而不是特定的Number類型其中T代表Type, 在定義泛型時(shí)通常用作第一個(gè)類型變量名稱. 但實(shí)際上T可以用做任何有效名稱代替, 除了T之外,以下是常見泛型變量代表的意思
K(Key): 表示對象中的鍵類型
V(Value): 表示對象中的值類型
E(Element): 表示元素類型
其實(shí)并不是只能定義一個(gè)類型變量, 我們可以引入希望定義的任何數(shù)量的類型變量. 比如我們引入一個(gè)新的類型變量U, 用于擴(kuò)展定義我們的identity函數(shù)
function identity4<T, U>(value: T, message: U): T {
console.info(message)
return value
}
console.log(identity4<Number, string>(68, 'Semlinker'))
除了為類型變量顯式設(shè)定值之外, 一種更常見的做法是使編譯器自動選擇這些類型, 從而使代碼更簡潔. 我們可以完全省略尖括號, 比如:
function identity5<T, U>(value: T, message: U): T {
console.info(message)
return value
}
console.log(identity5(68, 'Semlinker'))
對于上述代碼, 編譯器足夠聰明, 能夠知道我們的參數(shù)類型, 并將它們賦值給 T 和 U, 而不需要開發(fā)人員顯式指定它們. 如你所見, 該函數(shù)接收你傳遞給它的任何類型, 使得我們可以為不同類型創(chuàng)建可重用的組件. 現(xiàn)在我們再來看一下 identity 函數(shù):
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
相比之前定義的 identity 函數(shù), 新的 identity 函數(shù)增加了一個(gè)類型變量 U, 但該函數(shù)的返回類型我們?nèi)匀皇褂?T. 如果我們想要返回兩種類型的對象該怎么辦呢?針對這個(gè)問題, 我們有多種方案, 其中一種就是使用元組, 即為元組設(shè)置通用的類型:
function identity6<T, U>(value: T, message: U): [T, U] {
console.info(message)
return [value, message]
}
console.log(identity6(68, 'Semlinker'))
雖然使用元組解決了上述的問題, 但有沒有其它更好的方案呢?答案是有的, 你可以使用泛型接口
?? 泛型接口
為了解決上面提到的問題, 首先讓我們創(chuàng)建一個(gè)用于的 identity 函數(shù)通用 Identities 接口:
interface Identities<V, M> {
value: V
message: M
}
在上述的 Identities 接口中, 我們引入了類型變量 V 和 M, 來進(jìn)一步說明有效的字母都可以用于表示類型變量, 之后我們就可以將 Identities 接口作為 identity 函數(shù)的返回類型:
interface Identities<V, M> {
value: V
message: M
}
function identity<T, U>(value: T, message: U): Identities<T, U> {
console.info(`${value}: ${typeof value}`)
console.info(`${message}: ${typeof message}`)
let identities: Identities<T, U> = {
value,
message,
}
return identities
}
console.info(identity<Number, String>(68, 'finder'))
以上代碼成功運(yùn)行后, 在控制臺會輸出以下結(jié)果:
// 68: number
// Semlinker: string
// {value: 68, message: "Semlinker"}
?? 泛型類
在類中使用泛型也很簡單, 我們只需要在類名后面, 使用 <T, ...> 的語法定義任意多個(gè)類型變量, 具體示例如下:
interface PersonInterface<U> {
value: U
getIdentity: () => U
}
class IdentityClass<T> implements PersonInterface<T> {
value: T
constructor(value: T) {
this.value = value
}
getIdentity(): T {
return this.value
}
}
const p1 = new IdentityClass<string>('Leslie')
console.info(`p1 value ${p1.value}`) // p1 value Leslie
console.info(`p1.getIdentity() ${p1.getIdentity()}`) // p1.getIdentity() Leslie
const p2 = new IdentityClass<number>(1943)
console.info(`p2 value ${p2.value}`) // p2 value 1943
console.info(`p2.getIdentity() ${p2.getIdentity()}`) // p2.getIdentity() 1943
我們看下實(shí)例化p1的調(diào)用過程
在實(shí)例化IdentityClass的對象是, 我們傳入string類型和構(gòu)造函數(shù)參數(shù)值Leslie;
之后在IdentityClass類中, 類型變量T的值變成String類型
IdentityClass類實(shí)現(xiàn)了
PersonInterface<T>,而此時(shí)T表示String類型,因此等價(jià)于該類實(shí)現(xiàn)了PersonInterface<String>接口而對于
PersonInterface<U>來說, 類型變量U變成了String. 這里有意使用不同的變量名, 以表明類型值沿鏈向上傳播, 且與變量名無關(guān).泛型類可確保在整個(gè)類中一致地使用指定的數(shù)據(jù)類型. 比如, 你可能已經(jīng)注意到在使用 Typescript 的 React 項(xiàng)目中使用了以下約定:
type Props = {
className?: string
...
};
type State = {
submitted?: bool
...
};
class MyComponent extends React.Component<Props, State>{
}
在以上代碼中, 我們將泛型與 React 組件一起使用, 以確保組件的 props 和 state 是類型安全的. 相信看到這里一些讀者會有疑問, 我們在什么時(shí)候需要使用泛型呢?通常在決定是否使用泛型時(shí), 我們有以下兩個(gè)參考標(biāo)準(zhǔn): 當(dāng)函數(shù),接口或類將處理多種數(shù)據(jù)類型時(shí). 當(dāng)函數(shù),接口或類在多個(gè)地方使用該數(shù)據(jù)類型時(shí). 很有可能你沒有辦法保證在項(xiàng)目早期就使用泛型的組件, 但是隨著項(xiàng)目的發(fā)展, 組件的功能通常會被擴(kuò)展. 這種增加的可擴(kuò)展性最終很可能會滿足上述兩個(gè)條件, 在這種情況下, 引入泛型將比復(fù)制組件來滿足一系列數(shù)據(jù)類型更干凈.
關(guān)注公眾號添加鬼哥微信,和鬼哥一起學(xué)習(xí)
?? 看完三件事
如果你覺得這篇內(nèi)容對你挺有啟發(fā),不妨:
點(diǎn)個(gè)【在看】,或者分享轉(zhuǎn)發(fā),讓更多的人也能看到這篇內(nèi)容
點(diǎn)擊↓面關(guān)注我們,一起學(xué)前端
長按↓面二維碼,添加鬼哥微信,一起學(xué)前端
