TypeScript-TypeScript泛型编程

88 阅读5分钟

一、泛型语法的基本使用

1. 认识泛型

  • 我们可以通过函数来封装一些API,通过传入不同的函数参数,让函数帮助我们完成不同的内容
  • 类型参数化:封装一个函数,传入一个参数,并且返回这个参数

2. 泛型实现类型参数化

  • 类型变量,它作用于类型,而不是值
function foo<Type>(arg: Type): Type {
    return arg
}
  • 调用方式一:通过<类型>的方式将类型传递给参数
foo<string>("abc")
foo<number>(123)
  • 调用方式二:通过类型推导,自动推导出传入的变量的类型
foo("abc")
foo(123)

3. 泛型的基本补充

  • 可以传入多个类型
function foo<T, E>(a1: T, a2: E) {
}
  • 常用名称
    • T:Type的缩写,类型
    • K、V: key和value的缩写,键值对
    • E:Element的缩写,元素
      • O:Object的缩写,对象

二、泛型接口、类的使用

1. 泛型接口

  • 在定义接口的时候也可以使用泛型
interface IFoo<T> {
    initialValue: T,
    valueList: T[],
    handleValue: (value: T) => void
} 
const foo: IFoo<number> = {
    initialValue: 0,
    valueList: [0, 1, 3],
    handleValue: function(value: number) {
        console.log(value)
    }
}

2. 泛型类

  • 也可以编写泛型类
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)

三、泛型约束和类型条件

1. 泛型约束

  • 传入的类型必须有这个属性,也可以有其他属性,但是必须有这个成员
interface ILength {
    length: number
}
function getLength<T extends ILength>(args: T) {
    return args.length
}

2. 在泛型约束中使用类型参数

  • 可以声明一个类型参数,这个类型参数被其他类型参数约束
// 获取一个对象给定属性名的值
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
    return obj[key]
}
const info = {
    name: "why",
    age: 18
}
console.log(getProperty(info, "name")

四、TypeScript映射类型

1. 映射类型

  • 有时候,一个类型需要基于另外一个类型,但是又不想拷贝一份,这个时候可以考虑使用映射类型
  • 映射类型建立在索引签名的语法上
  • 映射类型,就是使用了PropertyKeys联合类型的泛型;其中PropertyKeys多是通过keyof创建,然后循环遍历键名创建一个类型
interface IPerson {
    name: string
    age: number
}
type MapType<Type> = {
    [property in keyof Type]: boolean
}
type NewPerson = MapType<IPerson>

2. 映射修饰符

  • readonly:用于设置属性只读
  • ?:用于设置属性可选
// 可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀
type MapType<Type> = {
    [property in keyof Type -? Type[property]
}
interface IPerson {
    name: string
    age: number
    height: number
}
type NewPerson = MapType<IPerson>

五、TypeScript条件类型

1. 条件类型

  • 条件类型就是帮助描述输入类型和输出类型之间的关系
// SomeType extends OtherType ? TrueType : FalseType
function sum<T extends number | string>(arg1: T, arg2: T): T extends string ? string : number
function sum(arg1: any, arg2: any) {
    return arg1 + arg2
}
const res1 = sum(10, 20)
const res2 = sum("aaa", "bbb")

2. 条件类型的类型推断

  • infer关键词,可以从正在比较的类型中推荐类型,然后在true分支里引用该推断结果
type CalcFnType = (num1: number, num2: number) => number

// 获取函数的返回值类型
type HYReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R: never
type CalcReturnType = HYReturnType<CalcFnType>

// 获取函数的参数类型
type HYParameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any? P: never
type CalcParameterType = HYParameters<CalcFnType>

3. 分支条件类型

  • 当在泛型中使用条件类型的时候,如果传入一个联合类型,就会变成分发的
// 如果在toArray传入一个联合类型,这个条件类型会被应用到联合类型的每个成员
type toArray<Type> = Type extends any ? Type[]: never
type newType = toArray<number | string>

六、类型工具和类型体操

1. Partial

  • Partial<Type>
  • 用于构建一个Type下面的所有属性都设置为可选的类型
interface IWhy {
    name: string,
    age: number,
    slogan?: string
}
// 自己定义的Partial类型 类型体操
type HYPartial<T> = {
    [P in keyof T]?: T[P]
}
// IWhy都变成可选的
type IWhyOptional = HYPartial<IWhy>

2. Required

  • Required<Type>
  • 用于构造一个Type下面的所有属性全都设置为必填的类型,这个工具类型与Partial相反
interface IWhy {
  name: string,
  age: number,
  slogan?: string
}

// 类型体操
type HYRequired<T> = {
  [P in keyof T]-?: T[P]
}

// IWhy都变成可选的
type IWhyRequired = HYRequired<IWhy>

3. Readonly

  • Readonly<Type>
  • 用于构造一个Type下面的所有属性全都设置为只读的类型,意味着这个类型的所有属性全都不可以重新赋值
interface IWhy {
  name: string,
  age: number,
  slogan?: string
}

// 类型体操
type HYReadonly<T> = {
  readonly [P in keyof T]?: T[P]
}

// IWhy都变成可选的
type IWhyReadonly = Readonly<IWhy>

4. Record

  • Record<Keys, Type>
  • 用于构造一个对象类型,它所有的key(键)都是Keys类型,它所有的value(值)都是Type类型
interface IWhy {
  name: string,
  age: number,
  slogan?: string
}

// 类型体操
type keys = keyof IWhy
type Res = keyof any // => number|string|symbol
// 确保keys一定是可以作为key的联合类型
type HYRecord<Keys extends keyof any, T> = {
  [P in Keys]: T
}

// IWhy都变成可选的
type t1 = "上海" | "北京" | "广东"
type IWhy2 = Record<t1, IWhy>

5. Pick

  • Pick<Type, Keys>
  • 用于构造一个类型,它是从Type类型里面挑了一些属性Keys
interface IWhy {
  name: string,
  age: number,
  slogan?: string
}

// 类型体操
type HYPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

type IWhy2 = HYPick<IWhy, "slogan"|"name">

6. Omit

  • Omit<Type, Keys>
  • 用于构造一个类型,它是从Type类型里面过滤了一些属性Keys
interface IWhy {
  name: string,
  age: number,
  slogan?: string
}

// 类型体操
type HYOmit<T, K extends keyof T> = {
  [P in keyof T as P extends K ? never: P]: T[P]
}

type IWhy2 = HYOmit<IWhy, "slogan"|"name">

7. Exclude

  • Exclude<UnionType, ExcludedMembers>
  • 用于构造一个类型,它是从UnionType联合类型里面排除了所有可以赋给ExcludedMembers的类型
type IWhy = "sing" | "dance" | "rap"

// 类型体操
type HYExclude<T, E> = T extends E? never: T

type IWhy2 = HYExclude<IWhy, "sing"|"rap">

8. Extract

  • Extract<Type, Union>
  • 用于构造一个类型,它是从Type类型里面提取了所有可以赋给Union的类型
type IWhy = "sing" | "dance" | "rap"

// 类型体操
type HYExtract<T, E> = T extends E? T: never

type IWhy2 = HYExtract<IWhy, "sing"|"rap">

9. NonNullable

  • NonNullable<Type>
  • 用于构造一个类型,这个类型从Type中排除了所有的null、undefined的类型
type IWhy = "sing" | "dance" | "rap" | null | undefined

// 类型体操
type HYNonNullable<T> = T extends null|undefined? never: T

type IWhy2 = HYNonNullable<IWhy>

10. InstanceType

  • InstanceType<Type>
  • 用于构造一个由所有Type的构造类型的实例类型组成的类型
class Person {}

const p1: Person = new Person()

// typeof Person: 构造函数具体的类型
// InstanceType构造函数创建出来的实例对象的类型
type HYPerson = InstanceType<typeof Person>
const p2: HYPerson = new Person()