Typescript 基础
2021-12-28 23:24:19

TS 泛型

参考文档:Generics

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

🅰️:一定不能将泛型与动态脚本语言中的动态类型混为一谈,泛型并不是没有类型,而是将声明类型的代码创建抽象的代码段中移动到了使用该抽象的代码段中。泛型的类型检查和静态类型一样是在编译阶段完成的。

🅰️:泛型的作用概括:增强代码的可复用性(抽象的同时保持代码强类型特性),增强代码类型安全(减少因类型问题产生的报错

泛型的使用

让我们看看 TS 中需要泛型的场景:

function echo(arg) {
	return arg
}

// 这时候我们发现了一个问题,函数将返回 any
// 但我们明明传入了一个数字!
const result = echo(123) // result: any

// 如何让函数返回其参数类型的数据呢?
// 我们使用泛型 + TS 类型声明即可完成此设计:
function echo<T>(arg: T): T {
  return arg
}
const result = echo(123) // result: number
const reslut = echo('hi!') // result: string

// 泛型也可以传入多个值
// 比如我们倒转传入的元组并返回
function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]]
}

const result = swap(['string', 123]) // 类型正确

泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法

// 例子
function echoWithArr<T>(arg: T): T {
	onsole.log(arg.length) // ?
	return arg
}

// 上例中,泛型 T 不一定包含属性 length
// 如何使用泛型约束增强类型安全呢?

// 声明一个接口对泛型进行约束
interface IWithLength {
  length: number;
}

// 使用 extends 关键字让泛型 T 继承接口
function echoWithLength<T extends IWithLength>(arg: T): T {
  console.log(arg.length)
  return arg
}

// 我们以此完成了泛型约束
// 将不带 length 属性的数据传入 tsc 将会捕捉到错误
echoWithLength('str')
const result3 = echoWithLength({length: 10})
const result4 = echoWithLength([1, 2, 3])

泛型与接口

// 泛型可以大大加强接口的可复用性
// 例子:键值对接口
interface KeyPair<T, U> {
	key: T;
    value: U;
}

let kp1: KeyPair<number, string> = { key: 1, value: "str"}
let kp2: KeyPair<string, number> = { key: "str", value: 123}

泛型与类

泛型最大的作用往往体现在类和接口上

// 例子:队列类
class Queue {
    private data = [];
    push(item) {
        return this.data.push(item)
    }
    pop() {
        return this.data.shift()
    }
}

const queue = new Queue()

// 我们应该如何约束队列内元素的类型呢?
queue.push(1)
queue.push('str')

// 在上述类允许你向队列中添加任何类型的数据,当数据被弹出队列时,也可以是任意类型。
// 这使得使用的过程中,会出现我们无法捕捉到的错误,例如:
// TS 无法捕捉到 string 类型没有 toFixed 方法的错误
console.log(queue.pop().toFixed())
console.log(queue.pop().toFixed())

// 如果人为给 data 一个类型声明,代码的复用性就会变差
class Queue {
    private data: number = [];
	// ...
} // x

// 此时泛型的作用就体现出来了
class Queue<T> {
	private data = [];
	push(item: T) {
        return this.data.push(item)
    }
    pop(): T {
        return this.data.shift()
    }
}

// 在使用时赋予 data 类型
const queue = new Queue<number>()