1. 程式人生 > 實用技巧 >泛型,很多人因它放棄學習 TypeScript?

泛型,很多人因它放棄學習 TypeScript?

1、ts的泛型很難嗎?

如果你:

  1. 剛開始學ts
  2. 剛開始接觸泛型
  3. 正在掙扎得學習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>()

其實和給函式引數設定預設值一樣:

functionmakeState<Sextendsnumber| string =number>()

這樣,變數state預設型別就是number了

const numState = makeState()
numState.setState(1)
console.log(numState.getState())

廣州品牌設計公司https://www.houdianzi.com PPT模板下載大全https://redbox.wode007.com

9、總結

泛型其實可以當作普通函式在宣告時的一個引數,這個引數代表型別。
我們可以給函式值引數設定預設值,
也可以通過typescipt的泛型給函式型別引數設定預設值。

function regularFunc(x = 2)
regularFunc()
function genericFunc<T = number>()
genericFunc()