泛型,很多人因它放弃学习 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的入参12替换成字符串'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())

问题:那么我们要如何限制它就只能输入输出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'.

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<T = number>()
genericFunc()
阅读 7.3k

推荐阅读