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