认识泛型

软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能

在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件

需要方法使返回值的类型与传入参数的类型是相同,使用 类型变量,特殊的变量,只表示类型而不是值

function foo<Type>(arg: <Type>): Type{
  return arg
}
// 参数类型为 number
const num = foo<number>(123)
// string
const str = foo<string>('hello')
// 类型推论,根据传入的参数自动确定Type的类型
const hello = foo('hello world') // type of output will be 'string'

foo 添加类型变量Type ,帮助我们捕获用户传入的类型(比如:number),我们再次使用 Tyep 当做返回值类型,知道参数类型与返回值类型是相同的

平时开发时没必要手动使用<类型>传,因为类型推论可以自动完成
如果编译器不能够自动地推断出类型的,才手动传入

默认值,像函数参数一样也可以赋上默认值

function foo<Type = string>(arg: Type): Type{
  return arg
}

平常使用类型变量名的缩写:

  • T : Type的缩写
  • K : key的缩写
  • V : value的缩写
  • E : Element的缩写
  • O : Object的缩写

泛型接口

在定义接口的时候我们也可以使用泛型

interface IFoo<T = number> {
  id: T,
  valueList: T[],
  handleValue: (value: T) => void
}

const foo: IFoo<number> = {
 id: 101,
 valueList: [1,2,3]
 handleValue: function(value: number){
   console.log(value)
  }
}

泛型类

泛型类与泛型接口差不多。 泛型类使用<>括起泛型类型,跟在类名后面

class Point<T> {
  x: T
  y: T

  constructor(x: T, y: T){
    this.x = x
    this.y = y
  }
  
}
// 类型推论
const p1 = new Point(10,20)
// 明确
const p2 = new Point<number>(10,20)
const p3: Point<number> = new Point(10,20)

泛型约束(Generic Constraints)

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

  • 比如string和array都是有length的,或者某些对象也是会有length属性的
  • 那么拥有length的属性都可以作为参数类型
interface ILength {
 length: number
}
function foo<T extends ILength>(arg: T): T{
  console.log(arg.length);
}

//泛型函数被定义了约束,不再是适用于任意类型
foo(123) // error
// 符合约束类型的值,必须包含必须的属性
foo({length: 10, value: 20})

在泛型约束中使用类型参数
举个栗子:我们希望获取一个对象给定属性名的值

  • 我们需要确保我们不会获取 obj 上不存在的属性
  • 所以我们在两个类型之间建立一个约束
// 声明Type类型参数,Key 类型参数继承 Type里的key
function foo<Type, Key extends Type keyof Type>(obj: Type, key: Key){
  console.log(obj[key])
}

const info = {
  name: 'kun'
  age: 12
}

foo(info,"name")
foo(info,"age")

映射类型(Mapped Types)

一个类型需要基于另外一个类型,但你又不想拷贝一份,这个时候可以考虑使用映射类型。

  • 大部分内置的工具都是通过映射类型来实现
  • 大多数类型体操的题目也是通过映射类型完成

映射类型建立在索引签名上

  • 映射类型,就是使用了 PropertyKeys 联合类型的泛型
  • PropertyKeys 多是通过 keyof 创建,然后循环遍历
interface IPerson {
  name: string
  age: number
}

type MapType<Type> = {
  [property in keyof Type]: any
}
type NewPerson = MapType<IPerson>

映射修饰符(Mapping Modifiers)

在使用映射类型时,有两个额外的修饰符可能会用到:

  • readonly: 用于设置属性只读;
  • ? : 用于设置属性可选;

修饰符可添加前缀 - 或者 + 删除或者添加这些修饰符
如果没有写前缀,相当于使用了 + 前缀

类型体操练习网站

网站1
网站2

条件类型(Conditional Types)

条件类型(Conditional types)就是用来帮助我们描述输入类型和输出类型之间的关系
写法类似 三目运算符
SomeType extends OtherType ? TrueType : FalseType

function foo<T extends number | string>(arg1: T, arg2: T):T extends string?string:number{
  console.log(arg+arg2)    
}

参考资料

TS中文网

coderwhy前端课程