泛型,很多人因它放棄學(xué)習(xí)TypeScript?
1、ts的泛型很難嗎?
如果你:
- 剛開(kāi)始學(xué)ts
- 剛開(kāi)始接觸泛型
- 正在掙扎得學(xué)習(xí)ts的泛型
看到以下代碼有沒(méi)有很疑惑?
- function makePair<
- F extends number | string,
- S extends boolean | F
- >()
Java是和typescript一樣支持泛型的,當(dāng)我在大學(xué)開(kāi)始學(xué)習(xí)Java的時(shí)候,我還是一個(gè)菜鳥(niǎo)碼農(nóng),遇到難點(diǎn)(比如泛型)就直接跳過(guò),能學(xué)多少學(xué)多少,回寢室就LOL開(kāi)黑。直到大學(xué)畢業(yè)我依舊沒(méi)有理解泛型的概念,可能你和我一樣覺(jué)得泛型很難,下面我會(huì)分享我的理解,希望對(duì)你有所幫助。
2、一起來(lái)看一下makeState()這個(gè)函數(shù)
首先,我寫(xiě)了makeState這個(gè)函數(shù),我們會(huì)用這個(gè)函數(shù)來(lái)討論泛型
- function makeState() {
- let state: number
- function getState() {
- return state
- }
- function setState(x: number) {
- state = x
- }
- return { getState, setState }
- }
當(dāng)你運(yùn)行這個(gè)函數(shù),我們會(huì)得到getState() 和 setState()這兩個(gè)函數(shù)。
讓我們來(lái)試一下,下面這段代碼會(huì)打印出什么
- const { getState, setState } = makeState()
- setState(1)
- console.log(getState())
- setState(2)
- console.log(getState())
- 1
- 2
會(huì)打印出1和2,沒(méi)那么難對(duì)吧?
Note: 如果你正在使用react,你可能會(huì)發(fā)覺(jué),makeState()和鉤子函數(shù)useState()很像。這里也涉及到了閉包和ES6的解構(gòu)賦值
3、我們傳入字符串會(huì)如何?
我們把剛才給setState的入?yún)?和2替換成字符串'foo'會(huì)輸出什么呢?
- const { getState, setState } = makeState()
- setState('foo')
- console.log(getState())
- Argument of type '"foo"' is not assignable to parameter of type 'number'.
會(huì)編譯失敗,因?yàn)閟etState()需要的參數(shù)類型是number
我們可以用以下方法解決這個(gè)問(wèn)題
- function makeState() {
- // Change to string
- let state: string
- function getState() {
- return state
- }
- // Accepts a string
- function setState(x: string) {
- state = x
- }
- return { getState, setState }
- }
- const { getState, setState } = makeState()
- setState('foo')
- console.log(getState())
- foo
4、挑戰(zhàn):獲取兩個(gè)不同類型的state
我們能不能修改makeState()這個(gè)函數(shù),來(lái)輸出兩個(gè)不同類型的state,比如一個(gè)是字符串,一個(gè)是數(shù)字。
以下代碼簡(jiǎn)略得表示我想表達(dá)的意思:
- // One that only allows numbers, and…
- const numState = makeState()
- numState.setState(1)
- console.log(numState.getState()) // 1
- // The other that only allows strings.
- const strState = makeState()
- strState.setState('foo')
- console.log(strState.getState()) // foo
要達(dá)到以上效果,我們可能需要?jiǎng)?chuàng)建兩個(gè)內(nèi)部不一樣的makeState(),一個(gè)state的類型是數(shù)字,一個(gè)是字符串。
怎么用才能只寫(xiě)一個(gè)來(lái)實(shí)現(xiàn)呢?
5、實(shí)驗(yàn)一:設(shè)置多個(gè)類型
這是我們的第一個(gè)嘗試:
- function makeState() {
- let state: number | string
- function getState() {
- return state
- }
- function setState(x: number | string) {
- state = x
- }
- return { getState, setState }
- }
- const numAndStrState = makeState()
- //數(shù)字
- numAndStrState.setState(1)
- console.log(numAndStrState.getState())
- //字符串
- numAndStrState.setState('foo')
- console.log(numAndStrState.getState())
- 1
- foo
結(jié)果看上去我們貌似成功了,但是這并不是我真實(shí)想要的,我們真正要實(shí)現(xiàn)的是只能輸出數(shù)字state和只能輸出字符串state。
numAndStrState是既能輸出數(shù)字類型,又能輸出字符串類型
6、實(shí)現(xiàn)二:使用泛型
接下來(lái)我們的泛型要登場(chǎng)了:
- function makeState<S>() {
- let state: S
- function getState() {
- return state
- }
- function setState(x: S) {
- state = x
- }
- return { getState, setState }
- }
makeState() 被定義成 makeState<S>(),你可以把<S>當(dāng)作函數(shù)參數(shù),但它傳入的不是值,而是類型。
比如你可以傳入數(shù)字類型:
- makeState<number>()
在makeSate()這個(gè)函數(shù)內(nèi)部state會(huì)變成數(shù)字類型
- let state: S // <- number
- function setState(x: S /* <- number */) {
- state = x
- }
這樣就實(shí)現(xiàn)了只能輸出數(shù)字state
- // Creates a number-only state
- const numState = makeState<number>()
- numState.setState(1)
- console.log(numState.getState())
- // numState.setState('foo') 輸入字符串foo會(huì)報(bào)錯(cuò)
同理我們也可以實(shí)現(xiàn)只能輸出字符串state
- // Creates a string-only state
- const strState = makeState<string>()
- strState.setState('foo')
- console.log(strState.getState())
- // strState.setState(1) 輸入數(shù)字1會(huì)報(bào)錯(cuò)
Note: 我們把makeState<S>()稱作泛型函數(shù),就是一個(gè)普通的函數(shù)支持類型參數(shù)的傳入
你可能會(huì)疑惑為什么類型參數(shù)是S, 其實(shí)隨便什么都可以,但是通常來(lái)說(shuō)我們會(huì)用一個(gè)變量的第一個(gè)字母的大寫(xiě)來(lái)代表這個(gè)變量的類型:
- T(for“T”ype)
- E(for“E”lement)
- K(for“K”ey)
- V(for“V”alue)
7、泛型的類型范圍限制
目前,在我們改進(jìn)下的makeState()實(shí)現(xiàn)了只能輸出數(shù)字state和只能輸出字符串state。但是它也能實(shí)現(xiàn)輸出布爾值。
- // Creates a boolean-only state
- const boolState = makeState<boolean>()
- boolState.setState(true)
- console.log(boolState.getState())
問(wèn)題:那么我們要如何限制它就只能輸入輸出number和string類型呢?
方法:聲明makeState()這個(gè)函數(shù)時(shí),把類型參數(shù)<S>變?yōu)?lt;S extends number | string>,這樣就只能輸入number或者string類型了
- function makeState<S extends number | string>() {
- let state: S
- function getState() {
- return state
- }
- function setState(x: S) {
- state = x
- }
- return { getState, setState }
- }
- // 如果我傳入boolean類型
- const boolState = makeState<boolean>()
- Type 'boolean' does not satisfy the constraint 'string | number'.
8、泛型的默認(rèn)類型
現(xiàn)在每次調(diào)用makeState()時(shí),我們可以任意傳入<number> 或<string>類型,那怎么設(shè)置一個(gè)默認(rèn)類型呢?
比如讓下面兩個(gè)語(yǔ)句起到相同的作用:
- const numState1 = makeState()
- const numState2 = makeState<number>()
其實(shí)和給函數(shù)參數(shù)設(shè)置默認(rèn)值一樣:
- function makeState<S extends number | string = number>()
這樣,變量state默認(rèn)類型就是number了
- const numState = makeState()
- numState.setState(1)
- console.log(numState.getState())
- 1
9、總結(jié)
泛型其實(shí)可以當(dāng)作普通函數(shù)在聲明時(shí)的一個(gè)參數(shù),這個(gè)參數(shù)代表類型。
我們可以給函數(shù)值參數(shù)設(shè)置默認(rèn)值,
也可以通過(guò)typescipt的泛型給函數(shù)類型參數(shù)設(shè)置默認(rèn)值。
- function regularFunc(x = 2)
- regularFunc()
- function genericFunc<TT = number>()
- genericFunc()