一、泛型是什么
1. 泛型的定义
软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。
2. 泛型举个🌰:
定义一个通用的 identity 函数,该函数接收一个参数并直接返回它:
function identity (value) {
return value;
}
console.log(identity(1)) // 1
将 identity 函数做适当的调整,以支持 TypeScript 的 Number 类型的参数:
function identity (value: Number) : Number {
return value;
}
console.log(identity(1)) // 1
这里 identity 的问题是我们将 Number 类型分配给参数和返回类型,使该函数仅可用于该原始类型。但该函数并不是可扩展或通用的,很明显这并不是我们所希望的。
我们确实可以把 Number 换成 any,我们失去了定义应该返回哪种类型的能力,并且在这个过程中使编译器失去了类型保护的作用。我们的目标是让 identity 函数可以适用于任何特定的类型,为了实现这个目标,我们可以使用泛型来解决这个问题,具体实现方式如下:
function identity <T>(value: T) : T {
return value;
}
console.log(identity<Number>(1)) // 1
3. T 的含义
对于刚接触 TypeScript 泛型的来说,首次看到 <T> 语法会感到陌生。但这没什么可担心的,就像传递参数一样,我们传递了我们想要用于特定函数调用的类型。
参考上面的图片,当我们调用 identity(1) ,Number 类型就像参数 1 一样,它将在出现 T 的任何位置填充该类型。图中 内部的 T 被称为类型变量,它是我们希望传递给 identity 函数的类型占位符,同时它被分配给 value 参数用来代替它的类型:此时 T 充当的是类型,而不是特定的 Number 类型。
其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:
K(Key):表示对象中的键类型;
V(Value):表示对象中的值类型;
E(Element):表示元素类型。\
4. 定义多个类型变量
其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U,用于扩展我们定义的 identity 函数
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<Number, string>(68, "Semlinker"));
除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:
注意:由 identity<Number, string>(68, "Semlinker")) 简写成 identity(68, "Semlinker")
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "Semlinker"));
二、泛型相关应用
1. 泛型函数,也叫泛型方法
a. 泛型函数返回类型为传入什么类型就返回什么类型
错误写法
function getDate<T>(value:T):T{
// 错误写法: “T”可以用与“string”无关的任意类型实例化。
return '123'
}
正确写法
function getDate<T>(value:T):T{
// 正确写法
return value
}
b. 泛型函数返回类型为任意类型
传入的参数可以限制数据类型, 返回的参数可以通过 any 设置任意数据类型返回
function getDate<T>(value:T):any{
return value;
}
console.log(getDate<number>(123)) // 123
console.log(getDate<string>('abc')) // abc
2. 类的泛型
通过类的泛型来定义类
class MinNum<T>{
list:T[] = []
add(num:T) {
this.list.push(num)
}
getMin():T {
var minNum = this.list[0]
for(var i = 0; i< this.list.length; i++) {
if(minNum > this.list[i]) {
minNum = this.list[i]
}
}
return minNum
}
}
//实例化类 并且制定了类的 T 代表的类型是 number
let b1 = new MinNum<number>()
b1.add(99)
b1.add(44)
b1.add(55)
b1.add(66)
b1.add(33)
b1.add(22)
console.log(b1.getMin())
// 实例化类,并且制定了类的 T 代表的类型是 string
let b2 = new MinNum<string>()
// js的字符串在进行大于(小于)比较时,会根据第一个不同的字符的ASCII码值进行比较
b2.add('c')
b2.add('bb')
b2.add('aa')
b2.add('tt')
b2.add('ee')
b2.add('r')
console.log(b2.getMin())
3. 泛型接口
a. 泛型接口定义的第一种写法
interface ConfigFn{
<T>(value:T):T
}
var getDate:ConfigFn=function<T>(value:T):T{
return value;
}
// 错误写法
// getDate<string>(123) // 类型为“number”的参数不可分配给类型为“string”的参数。
// getDate<number>('abc') // 类型为“string”的参数不可分配给类型为“number”的参数。
// 正确写法
getDate<string>('abc')
getDate<number>(123)
b. 泛型接口定义的第二种写法
interface ConfigFn<T> {
(value:T):T;
}
function getDate<T>(value:T):T{
return value
}
var myDate:ConfigFn<string> = getDate
// 错误写法
// myDate(24)
// 正确写法
myDate('abc')
var myDate2:ConfigFn<number> = getDate
// 错误写法
// myDate2('456')
// 正确写法
myDate2(123)
4. 把类作为参数类型的泛型类
把类作为参数来约束数据传入的类型
a. 第一种写法: 把类作为参数类型的泛型类
class User{
// username: string; 这样写的话会报错误:属性“username”没有初始值设定项,也没有在构造函数中明确指定。
username: string | undefined;
password: string | undefined;
}
class MySqldb{
add(use:User):boolean{
console.log(use, 'use')
return true
}
}
let info = new User()
info.username = 'xxl'
info.password = '123456'
let db = new MySqldb()
db.add(info)
图示:
b. 第二种写法: 把类作为参数类型的泛型类
class User{
// username: string; 这样写的话会报错误:属性“username”没有初始值设定项,也没有在构造函数中明确指定。
username: string | undefined;
password: string | undefined;
}
class MySqldb<T>{
add(use:T):boolean{
console.log(use, 'use')
return true
}
}
let info = new User()
info.username = 'xxl'
info.password = '123456'
let db = new MySqldb<User>()
db.add(info)
图示: