什么是泛型越妈?
泛籍嘹,指多
簡單來說就是多種類型
只要你能看懂 JS 的函數(shù),那么你就能看懂 TS 的泛型
JS:
const fn = (a, b) => a + b
const result = fn(1,2) // 3
TS:
type Fn<A, B> = A | B
type Result = Fn<string, number> // string | number
很像把瑟俭,格式上看起來一模一樣,非常簡單
思考:函數(shù)的本質(zhì)是什么契邀?
不知道大家平時(shí)在寫函數(shù)的時(shí)候摆寄,有沒有想過這個(gè)問題?
- 復(fù)用代碼坯门?那我聲明就了一次微饥,也沒復(fù)用啊
- 抽離邏輯?為了代碼更好看古戴?
- 輸入輸出欠橘?這些目的是什么?不用函數(shù)我也能輸入輸出
個(gè)人認(rèn)為:函數(shù)的本質(zhì)是退后執(zhí)行的现恼、部分待定的代碼
console.log(1)
當(dāng)這部分代碼被解析的時(shí)候肃续, console 就已經(jīng)執(zhí)行了黍檩,那我們想晚一點(diǎn)執(zhí)行呢?用函數(shù)
const fn = () => console.log(1)
setTimeout(() => fn(), 2500)
所以函數(shù)的本質(zhì)之一就是:在我們這種面相過程的編程里面痹升,把執(zhí)行時(shí)機(jī)往后推遲
再繼續(xù)看建炫,還是這段代碼,這里的 fn 函數(shù)體非常死板疼蛾,只能打印出 1,于是我們改造:
const fn = (x) => console.log(x)
fn(222)
fn(333)
所以函數(shù)的第二個(gè)特點(diǎn)就是部分待定的代碼艺配,其實(shí)還是往后推遲察郁,這里的 console 我不想立馬確定,我希望能想什么時(shí)候執(zhí)行就什么時(shí)候執(zhí)行转唉,想 log 幾就 log 幾
舉一反三:泛型的本質(zhì)
泛型的本質(zhì)是退后執(zhí)行的皮钠、部分待定的類型
實(shí)例
function echo(n: number | string | boolean) {
return n
}
實(shí)際上我們希望這個(gè)函數(shù)的類型是傳入什么類型就返回什么類型
- echo(n: number) => number
- echo(n: string) => string
- echo(n: boolean) => boolean
很遺憾,如果不用泛型做不到赠法,即使你函數(shù)里面加了判斷
function echo(n: number | string | boolean) {
if (typeof n === 'number'){
return n
} else if (typeof a === 'string') {
return n
} else {
return n
}
}
依然存在錯(cuò)亂的情況
沒有泛型麦轰,有些奇怪的需求就無法滿足
沒有泛型的類型系統(tǒng),就如同 JS 沒有函數(shù)
泛型-簡單難度
type Union<A, B> = A | B
type Intersect<A, B> = A & B
type List<T> = { // 就像函數(shù)一樣
[key: string]: T
}
type Person = { name: string; age: number; }
List<Person> // 能匹配下面這些數(shù)據(jù)
{
a: Person,
b: Person,
...
}
默認(rèn)值
既然函數(shù)可以有默認(rèn)值砖织,泛型能有么款侵?當(dāng)然有
type List<T = string> = {
[key: string]: T
}
泛型-中等難度
來看一道題目
type Person = { name: string; }
type LikeString<T> = ??? // 如果這個(gè) T 是 string,則返回 true侧纯,否則 false
type LikeNumber<T> = ??? //如果這個(gè) T 是 number新锈,則返回 true,否則 false
type LikePerson<T> = ??? // 如果這個(gè) T 是 Person眶熬,則返回 true妹笆,否則 false
type R1 = LikeString<'hi'> // true
type R2 = LikeString<true> // false
type S1 = LikeNumber<666> // 1
type S2 = LikeNumber<false> // 2
type T1 = LikePerson<{ name: 'hi', xxx: 1 }> // yes
type T2 = LikePerson<{ xxx: 1 }> // no
我們先想一下如果在 JS 中怎么實(shí)現(xiàn)
const likeString = a => typeof a === 'string' ? true : false
那么在 TS 中怎么實(shí)現(xiàn)呢?
type LikeString<T> = T extends string ? true : false
疑惑點(diǎn)來了娜氏,為何 TS 中的判斷要用 extends拳缠,為什么不能像 JS 那樣使用 ===,多有語義化
其實(shí)這跟類型兼容贸弥、父類型窟坐、子類型有關(guān)系,在類型中很多時(shí)候我們做不到完全一致茂腥,因?yàn)榧吓c集合之間相等的時(shí)候是很少的狸涌,大多時(shí)候是從屬關(guān)系,所以我們把這里的 extends 我更建議讀作包含于
剩下的實(shí)現(xiàn)就很簡單了:
type LikeNumber<T> = T extends number ? 1 : 2
type LikePerson<T> = T extends Person ? 'yes' : 'no'
其中會(huì)有這幾條規(guī)則
- 若 T 為 never最岗,則表達(dá)式的值為 never
- 若 T 為聯(lián)合類型帕胆,則分開計(jì)算
第一條規(guī)則:
type R1 = LikeString<never> // never
違背常理的事情又發(fā)生了,三元表達(dá)式竟然多計(jì)算出了一種結(jié)果般渡!竟然返回了 never懒豹!
第二條規(guī)則:
type ToArray<T> = T extends unknown ? T[] : never
type Result = toArray<string | number>
思考:此時(shí) Result 的類型是什么
- Result 為 (string | number)[]
- Result 為 string[] | number[]
答案是 2芙盘,為 string[] | number[],即為:
type Result = toArray<string | number>
type Result = string extends unknown ? string[] : never
|
number extends unknown ? number[] : never
為什么 TS 會(huì)這么推導(dǎo)呢脸秽?一開始我也很不理解儒老,直至后來我用函數(shù)來理解
偽代碼:
function fn(a: T): ToArray<T> {
if (type a === 'string') {
return string[]
} else if (typeof a === 'number') {
return number[]
}
}
實(shí)際上我們經(jīng)常會(huì)寫出這樣的函數(shù),根據(jù) a 的類型來判斷返回值的類型记餐,所以 TS 的預(yù)判是正確的
由此可推驮樊,上面返回的 never 也是合理的:
function fn(a: T): ToArray<T> {
if (type a === 'string') {
return string[]
} else if (typeof a === 'number') {
return number[]
} else {
return never // TODO: 注意看這里,你傳了一個(gè) never 進(jìn)來片酝,自然而然就是返回 never
}
}
注意上述兩條規(guī)則只對(duì)泛型有效(上面的代碼也一直在用函數(shù)的思路來舉例)
type X = never extends unknown ? 1 : 2 // 1
在加大一點(diǎn)難度
獲取對(duì)象的 key
type Person = { name: string; age: number; }
type GetKeys<T> = keyof T
type Result = GetKeys<Person> // name | age
判斷兩個(gè)類型是否相等
type Eq<A, B> = A extends B ? B extends A ? true : false : false
思路就是判斷 A 是否包含于 B
且 B 是否包含于 A
囚衔,那么為 true,否則為 false
很遺憾雕沿,不完全對(duì)练湿,看起來很正確,其實(shí)因?yàn)?分開計(jì)算 的規(guī)則下审轮,得到的并不是這樣的結(jié)果
獲取對(duì)象的 value
type Person = { name: string; age: number; }
type GetKeyType<T, K extends keyof T> = T[K]
type Result = GetKeyType<Person, 'name'> // string