# 泛型

比如定义一个可复用性方法,但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()
  }
}

思想:面向对象编程,要做什么,首先想到这个对象需要什么属性和方法,然后一步一步的去实现其方法,然后再组装成一个可实用的对象。定义约束,根据所需要的属性和方法去设置整体类的接口,属性的类型,方法的传参,需要什么设置什么,不确定的就用泛型来代替。