typescript - 泛型

91 阅读2分钟

软件工程的主要目的是构建不仅仅明确和一致的API,还要让你的代码具有很强的可重用性

比如我们可以通过函数来封装一些API,通过传入不同的函数参数,让函数帮助我们完成不同的操作

所以对于函数而言,其参数的类型往往是不固定的,此时我们就可以使用泛型

所谓泛型,其本质就是将参数类型变量化的过程

// Type 就是一个类型变量 其具体值取决于外部传入的值
// 参数变量命名 一般是大驼峰变量 如Type 或大写的字母 如T
// 泛型和函数参数一样 是可以设置对应的默认值的
// 如果泛型没有设置默认值,且没有传参的时候,默认值会被推导为unknown
function foo<Type = string>(arg: Type): Type {
  return arg
}

const res = foo<string>('Klaus') // typeof res -> string

// 默认情况下,ts可以根据我们传入的值的类型推测出正确的类型参数的类型
const num1 = foo(111) // typeof num1 -> 111
let num2 = foo(222) //  typeof num2 -> number

常见的泛型参数名

名称说明
TType的缩写,类型
K, Vkey和value的缩写,键值对
EElement的缩写,元素
OObject的缩写,对象
RReturnType的缩写,函数返回值

示例

泛型接口

// 泛型参数不仅仅可以定义在整个接口上(全局泛型)
// 也可以定义在泛型成员上(局部泛型)
interface IFoo<T> {
  name: T
}

// 对于接口泛型 --- ts并不能自动推导,所以需要手动指定
const foo: IFoo<string> = {
  name: 'Klaus'
}

泛型类

class Foo<T> {
  name: T

  constructor(name: T) {
    this.name = name
  }
}

const foo = new Foo('Klaus')
console.log(foo.name) // typeof foo.name -> string

泛型约束

有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中

比如string和array都是有length的,或者某些对象也是会有length属性

如果我们希望一个类型可以同时满足上述所有条件,此时就可以使用泛型约束

function foo(arg: { length: number }) {
  return arg
}

/*
  此时res1和res2会丢失返回的类型
  此时res1 和 res2 的类型 都是 { length: number }
*/
const res1 = foo('123')
const res2 = foo([1, 2, 3])
function foo<T extends { length: numbr }>(arg: T) {
  return arg
}

// 因为原本的类型使用泛型变量T保存了下来,且使用extends关键字进行了约束
// 所以此时类型即满足了约束,且不会丢失原本的类型
const res1 = foo('123') // typeof res1 -> '123'
const res2 = foo([1, 2, 3]) // typeof res2 -> [1, 2, 3]
function getV<O, K extends keyof O>(obj: O, key: K) {
  return obj[key]
}

const user = {
  name: 'Klaus',
  age: 23
}

const name = getV(user, 'name')

export {}