xinglee23/Blogs

泛型

Opened this issue · 0 comments

泛型(Generics)

什么是泛型?
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

1、在认识泛型之前,我们先来考虑考虑动机,泛型是怎么样出现的,它要解决怎样的问题。我们先看下面一个函数,我们定义一个echo函数,传入一个参数然后返回这个参数,但是返回的参数我们没有办法知道其类型。

先看个函数

function echo(arg) {
    return arg
}

const result = echo('str')

像这种传入number返回的却是string

const result: string = echo('str')
  • 怎么解决这个问题呢,当我们想返回值为number的时候我们可以这样写:
function echo(arg: number): number {
    return arg
}
  • 当我们想返回值为string的时候我们可以这样写:
function echo(arg: string): string {
    return arg
}
  • 这样的话代码没复用不简洁,不符合代码规范,也不是我们想要的结果。当然我们也可以用any,但是我们的传入和返回没法做到统一,怎么解决这个问题呢,这个时候就要用到我们的泛型了

  • 下面我们来写第一个泛型:

function echo<T>(arg: T): T {
    return arg
}

const str: string = 'str'
const result = echo(str)

更常见的写法是下面这样,直接传入string或者其他类型,利用ts的类型推断

const result = echo('str')

当然我们的泛型也可以传入多个值,上次我们学习了与元组,我们可以利用元组定义不同的参数。泛型定义的时候也可以一次定义多个参数

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [truple[1], turple[0]]
}

swap(7, 'seven') // ['seven', 7]

以上就是泛型的简单介绍,接下来我们看看什么是约束泛型

  • 我们先来看一个🌰
function echoWhithArr<T>(arg: T): T {
    console.log(arg.length)
    return arg
}
  • 我们事先并不知道arg的类型所以并不知道arg有没有length属性(arg可能为number,boolean等),所以在函数调用length属性的时候函数会报错
  • 一种解决方法是我们指定传入的参数是有Array类型,代码如下:
function echoWhithArr<T>(arg: T[]): T[] {
    console.log(arg.length)
    return arg
}

const arrs = echoWhithArr[1, 2, 3]
  • 以上是一种解决方案,但是我们只能传入数组,没办法传入字符串,对象。我们需要一个新的解决方案,我们需要对泛型进行一个约束,只允许函数传入那些包含length属性的变量,这就是约束泛型。
  • 首先我们新建一个interface
interface IWithLength {
    length: number
}
  • 我们泛型用上面的接口进行约束,使用 extends 关键只代码如下,我们可以传入那些有length属性的参数了:
function echoWhithArr<T extends IWithLength>(arg: T): T {
    console.log(arg.length)
    return arg
}

const str = echoWhithArr('str)
const obj = echoWhithArr({ length: 10 })
const arr = echoWhithArr([1, 2, 3])

2、类的泛型

  • 上面我们演示的泛型都是作用在函数中,在函数的参数和返回值当中,下面我们来看看泛型在其他方面的应用:
  • 第一个是类中的应用,我们来看看下面这个例子
class Quene {
    private data = []
    pop(item) {
        return this.data.push(item)
    }
    push() {
        return this.data.shift()
    }
}

const quene = new Quene()
quene.push(12)
quene.push('str')
console.log(quene.pop().toFixed())
console.log(quene.pop().toFixed())
  • 上面的函数表面上看起来没什么问题,但是执行会出错,因为第二次取出的参数是字符串,而字符串是没有toFixed方法的。
  • 首先我们想到的解决方法是将类里面的方法指定为number类型,这样就不会出现刚才的错误
class Quene {
    private data = []
    pop(item: number) {
        return this.data.push(item)
    }
    push(): number {
        return this.data.shift()
    }
}
  • 但是如果我们要使用其他类型的队列,我们不得不修改刚才的代码。但是我们真正想要的是无论什么类型被推入队列,被推出的类型和推入的类型保持一致。这时候泛型可以帮助我们
  • 这时候我们可以在类名后面加上尖括号,如下所示
class Quene<T> {
    private data = []
    pop(item: T) {
        return this.data.push(item)
    }
    push(): T {
        return this.data.shift()
    }
}

我们实例化泛型类的时候指定其类型,这样在pop出来的时候调用该类型的方法的时候会有提示。

const queue = new Quene<number>()
quenue.push(1)
console.log(quene.pop().toFixed())

const queue = new Quene<string>()
quenue.push('str')
console.log(quene.pop().length)

3、接口的泛型

  • 既然类可以定义成泛型,那我们的老朋友接口也可以接受泛型的洗礼,让我们一起来看一看
  • 我们定义一个接口,在使用它的时候我们可以灵活的指定其类型,是不是很方便。
interface KeyPair<T, U> {
    key: T
    value: U
}

let kp1: KeyPair<number, string> = { key: 123, value: 'str' }
let kp1: KeyPair<string, number> = { key: '123', value: 123 }
  • 同样的接口还可以作用在数组里面,一般我们定义数组是下面这样的
let arrr: number[] = [1, 2, 3]
  • 我们也可以利用泛型,Array是ts内置的泛型,我们在编辑器里面点击它会出现相关的接口,感兴趣的可以看看
let arrTwo: Array<number> = [1, 2, 3]
  • 同样interface可以描述函数的类型
  • 通常我们使用接口定义方法的类型时,是如下的
interface IPlus {
    (a: number, b: number) : number
}
function plus(a: number, b: number): number {
    return a + b
}

const a: IPlus = plus
  • 但是如果我们想要接口可以复用性更高,我们可以在接口里面使用泛型,在使用的时候指定其类型,非常灵活,可以复用
interface IPlus<T> {
    (a: T, b: T) : T
}
function plus(a: number, b: number): number {
    return a + b
}
function connect(a: string, b: string): string {
    return a + b
}

const a: IPlus<number> = plus
const b: IPlus<string> = connect

总结:

  • 泛型是指我们在定义类,接口,函数的时候不指定其类型,而是在调用的时候指定其类型!
  • 泛型主要有函数的泛型、泛型的约束、类的泛型、接口的泛型
  • 类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型