# 泛型
比如定义一个可复用性方法,但ts类型检查不能支持多种类型,所以就通过泛型可以让其支持任何类型检查并保证可复用性。并且也支持未来的未知类型,如果用any就没办法进行类型检查。
通俗理解: 泛型就是解决类 接口 方法的复用性、及对不特定数据类型的支持
一般用 T
定义泛型,也可以叫其他的,具体什么类型是根据调用方法时传入的类型决定的
# 泛型函数
定义一个函数,传入什么类型返回什么类型,或者传入什么类型返回任意类型
// 传入什么返回什么
function getData<T>(value:T):T {
return value
}
getData<number>(123); // <number> 可以省略
getData('chagnzhen');
// 传入特定类型返回任意类型
function getData2<T>(value:T):any {
return value+'xxxx'
}
getData2(123);
// 多个泛型
function add<T, K>(a: T, b: K):Array<T | K> {
return [a, b]
}
add(1, false)
// 默认值
function add<T = number, K = number>(a: T, b: K):Array<T | K> {
return [a, b]
}
// 解析为function add<number, number>(a: number, b: number): number[]
# 类型别名泛型
type A<T> = string | number | T
let a1:A<boolean> = 'xxx'
let a2:A<boolean> = true
let a2:A<undefined> = undefined
# 泛型接口
// 对象接口
interface Date<T> {
msg: T
type: boolean
}
let date:Date<string> = {
msg: '2022-01-01',
type: false
}
// 函数接口
// interface Config {
// (key: string, value: string):string;
// }
interface ConfigFn {
<T>(value:T):T;
}
let getData3:ConfigFn = function<T>(value:T):T {
return value
}
getData3('zhangsan');
// 第二种写法
interface ConfigFn2<T> {
(value:T):T;
}
function getData4<T>(value:T):T {
return value
}
// 其实可以直接定义泛型函数就行了
var myGetData4:ConfigFn2<string> = getData4;
myGetData4('chagnzhen');
// 类接口
interface Animal<T> {
name: T
eat(str?:T):void
}
// 类必须要有name和eat
class Dog implements Animal<string> {
name:string
constructor(name:string) {
this.name = name
}
eat(food?:string):void {
console.log(`${this.name}eat ${food}`)
}
}
# 泛型类
class MinClass {
list:number[] = [];
add(num: number):void {
this.list.push(num)
}
min():number {
let minNum = Math.min.apply([], this.list)
return minNum;
}
}
var m = new MinClass();
m.add(2);
m.add(8);
m.add(5);
m.add(0);
console.log(m.min());
// 使用泛型定义
class MinClassT<T> {
list:T[] = [];
add(num:T):void {
this.list.push(num)
}
min():T {
let minNum = this.list[0]
for (let i = 0; i < this.list.length; i++) {
if (minNum > this.list[i]) {
minNum=this.list[i]
}
}
// minNum = Math.min.apply([], this.list)
return minNum;
}
}
var mx = new MinClassT<number>(); // 实例化类 并制定了类的T代表的类型是number
mx.add(5);
mx.add(3);
mx.add(9);
console.log(mx.min());
var ms = new MinClassT<string>();
ms.add('a');
ms.add('z');
ms.add('d');
console.log(ms.min());
# 把类作为参数类型的泛型类
把类作为参数来约束参数的泛型类
class User {
name: string | undefined;
age: number | undefined;
}
class MySqlDb {
add(user: User):boolean {
console.log(user);
return true
}
}
var user = new User();
user.name = 'zhangsan';
user.age = 18;
var mysql = new MySqlDb();
mysql.add(user); // 把User作为传入参数来校验类型
// 定义泛型类
class MyDb<T> {
add(user: T):boolean {
console.log(user);
return true
}
}
var mydb = new MyDb<User>(); // 根据不同的类来校验传入数据类型
mydb.add(user);
// mydb.add('xxxx'); // 以User类作为类型校验,传入其他的报错
# 泛型约束
泛型有时也太灵活了,传入类型都可以,显然有些类型是不适应的,比如某些类型可以.length,有些不行,这时我们可以使用 extends
来进行约束,限制要传的类型。
// 限制number/string 类型才能相加
// 不写返回类型ts会自动推断 function add<T extends number>(a: T, b: T): number[]
function add<T extends number>(a: T, b:T) {
return [a + b]
}
// 约束有length属性
interface Len {
length: number
}
function fn<T extends Len>(a: T) {
a.length
}
fn('1111')
fn([1, 2, 3])
keyof
我们可以使用 keyof
关键字可以获取某种类型的所有键,其返回类型是联合类型,从而约束某个属性是否属于这个对象。
type User = {
name: string
age: number
}
type key = keyof User // number | string
let obj = {
name: 'xiaoming',
age: 18
}
// type Key = keyof typeof obj // 推断为联合类型 number | string
// 获取某个对象的某个属性值
function ob<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key]
}
ob(obj, 'name')
高级用法
interface Data {
name: string,
age: number,
sex: string
}
// 使用 key in 循环 Data接口,遍历成一个可选的类型别名
type Options<T extends object> = {
[Key in keyof T]?:T[Key]
// readonly [Key in keyof T]:T[Key] 或者
}
type C = Options<Data>
// type C = {
// name?: string | undefined;
// age?: number | undefined;
// sex?: string | undefined;
// }
let c:C = {
name: 'xxx',
age: 18
}
# 泛型栗子
1、数据库操作
定义一个操作数据库的库,支持MySQL、Mssql、MongoDB等,要求数据库功能一样,都有 add update delete get 方法,注意约束统一规范,以及代码重用。
解决方案:需求约束规范所以要定义接口,需要代码重用所以用到泛型
// 先约束一个操作接口
interface DBI<T> {
add(info:T):boolean;
update(info:T,id: number):boolean;
delete(info:number):boolean;
get(info:number):boolean;
}
// 定义操作MySQL数据库的类
// 注意: 要实现泛型接口,这个类也应该是一个泛型类
class MySQL<T> implements DBI<T> {
add(info: T): boolean {
console.log(info)
return true
}
update(info: T, id: number): boolean {
throw new Error("Method not implemented.");
}
delete(info: number): boolean {
throw new Error("Method not implemented.");
}
get(info: number): boolean {
throw new Error("Method not implemented.");
}
}
// mssql
class MsSQL<T> implements DBI<T> {
add(info: T): boolean {
throw new Error("Method not implemented.");
}
update(info: T, id: number): boolean {
throw new Error("Method not implemented.");
}
delete(info: number): boolean {
throw new Error("Method not implemented.");
}
get(info: number): boolean {
throw new Error("Method not implemented.");
}
}
// 操作用户表 定义一个User类和数据表做映射
class User1 {
name: string | undefined;
age: number | undefined;
}
let u = new User1();
u.name = 'chang';
u.age = 19;
// 传入一个User表,对其增删改查
var oMySQL = new MySQL<User1>();
oMySQL.add(u)
2、封装localStorage
封装一个可以设置过期时间的localStorage插件。
// 定义一个字典
enum Dictionaries {
permanent = 'permanent',
expire = '__expire__'
}
type Key = string // key 类型
// 过期时间类型 永久 或者时间戳
type Expire = Dictionaries.permanent | number
// 约束get获取内容
interface Data<T> {
value: T
[Dictionaries.expire]: Expire
}
// 约束get返回内容
interface Result<T> {
msg: string
value: T | null
}
// 定义 Storage 类类型
interface StorageCls {
set<T>(key: Key, value: T, expire: Expire):void
get<T>(key: Key): Result<T | null>
remove(key: Key):void
clear():void
}
// 导出Storage类
export class Storage implements StorageCls {
// set接收三个参数 key: string value任意值为一个泛型T expire过期时间 永久或者时间戳
set<T>(key: Key, value: T, expire: Expire): void {
const data = {
value,
[Dictionaries.expire]: expire
}
localStorage.setItem(key, JSON.stringify(data))
}
// get 接收一个参数key 读取值和返回值都为set保存的类型,所以也要定义一个泛型
get<T>(key: Key): Result<T> {
const value = localStorage.getItem(key)
if (value) {
const data: Data<T> = JSON.parse(value) // 读取的值,为用户传入的值
const now = new Date().getTime()
// 判断是否过期
if (typeof data[Dictionaries.expire] === 'number' && data[Dictionaries.expire] < now) {
this.remove(key)
return {
msg: `你的${key}已过期`,
value: null
}
} else {
return {
msg: '成功',
value: data.value // 返回结果为用户存入类型
}
}
} else {
return {
msg: '值无效',
value: null
}
}
}
remove(key: Key): void {
localStorage.removeItem(key)
}
clear(): void {
localStorage.clear()
}
}
思想:面向对象编程,要做什么,首先想到这个对象需要什么属性和方法,然后一步一步的去实现其方法,然后再组装成一个可实用的对象。定义约束,根据所需要的属性和方法去设置整体类的接口,属性的类型,方法的传参,需要什么设置什么,不确定的就用泛型来代替。