泛型,不少人因它放棄學習 TypeScript?

一、ts的泛型很難嗎?

若是你:react

  1. 剛開始學ts
  2. 剛開始接觸泛型
  3. 正在掙扎得學習ts的泛型

看到如下代碼有沒有很疑惑?typescript

function makePair<
  F extends number | string,
  S extends boolean | F
>()

Java是和typescript同樣支持泛型的,當我在大學開始學習Java的時候,我仍是一個菜鳥碼農,遇到難點(好比泛型)就直接跳過,能學多少學多少,回寢室就LOL開黑。直到大學畢業我依舊沒有理解泛型的概念閉包

可能你和我同樣以爲泛型很難,下面我會分享個人理解,但願對你有所幫助。函數

二、一塊兒來看一下makeState()這個函數

首先,我寫了makeState這個函數,咱們會用這個函數來討論泛型學習

function makeState() {
  let state: number
  function getState() {
    return state
  }
  function setState(x: number) {
    state = x
  }
  return { getState, setState }
}

當你運行這個函數,咱們會獲得getState()setState()這兩個函數。code

讓咱們來試一下,下面這段代碼會打印出什麼ip

const { getState, setState } = makeState()
setState(1)
console.log(getState())
setState(2)
console.log(getState())
1
2

會打印出1和2,沒那麼難對吧?ci

Note: 若是你正在使用react,你可能會發覺, makeState()和鉤子函數 useState()很像。這裏也涉及到了 閉包和ES6的 解構賦值

三、咱們傳入字符串會如何?

咱們把剛纔給setState的入參12替換成字符串'foo'會輸出什麼呢?字符串

const { getState, setState } = makeState()
setState('foo')
console.log(getState())
Argument of type '"foo"' is not assignable to parameter of type 'number'.

會編譯失敗,由於setState()須要的參數類型是numberget

咱們能夠用如下方法解決這個問題

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

四、挑戰:獲取兩個不一樣類型的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的類型是數字,一個是字符串。
怎麼用才能只寫一個來實現呢?

五、實驗一:設置多個類型

這是咱們的第一個嘗試:

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是既能輸出數字類型,又能輸出字符串類型

六、實現二:使用泛型

接下來咱們的泛型要登場了:

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)

七、泛型的類型範圍限制

目前,在咱們改進下的makeState()實現了只能輸出數字state只能輸出字符串state。可是它也能實現輸出布爾值。

// Creates a boolean-only state
const boolState = makeState<boolean>()
boolState.setState(true)
console.log(boolState.getState())

問題:那麼咱們要如何限制它就只能輸入輸出numberstring類型呢?

方法:聲明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'.

八、泛型的默認類型

如今每次調用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

九、總結

泛型其實能夠看成普通函數在聲明時的一個參數,這個參數表明類型。
咱們能夠給函數值參數設置默認值,
也能夠經過typescipt的泛型給函數類型參數設置默認值。

function regularFunc(x = 2)
regularFunc()
function genericFunc<T = number>()
genericFunc()
相關文章
相關標籤/搜索