TS-系列-泛型

4,267 阅读7分钟

这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战

介绍

  • 泛型是可以在保证类型安全的前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数接口class中 示例
  • 需求 创建一个函数id,传入什么就返回什么
function id(num: number): number {
    return num
}

问题

  • 当前代码我们只能传入数组类型的数据
  • 如果我们传入字符串就会报错,这时any可以解决这个问题,但是使用any我们的类型检查就会失效

初体验

创建示例

function id<T>(num: T): T {
    return num
}

说明

  • 语法:在函数名称后面添加<>尖括号,尖括号中添加类型变量,比如上边的T
  • 类型变量T是一种特殊类型变量,他处理类型而不是值
  • 该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体什么类型由用户调用该函数时指定)
  • 因为T是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型。
  • 类型变量T,可以是任意合法的变量名称 调用示例
function id<T>(num: T): T {
    return num
}
const sum = id<number>(10)
const str = id<string>('hello')

说明

  • 语法:在函数名称后面添加<>尖括号,尖括号中指定具体的类型,比如,numberstring
  • 当传入 number类型后,这个类型就会被函数声明时指定的类型变量T捕获到
  • 此时,T的类型就是传入的变量类型,那么他们的返回值也是传入的变量类型
  • 这样,通过泛型就做到了让id函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全 简化调用示例
function id<T>(num: T): T {
    return num
}
const sum = id(10)
const str = id('hello')

说明

  • 在调用泛型函数时,可以省略<类型>来简化泛型函数的调用
  • 此时,TS内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出类型变量T的类型
  • 比如,入参是10,TS会自动推断出它属于number类型,并作为T的类型
  • 使用这种简化的方法调用泛型函数,使得代码更加的简短,更容易使人阅读
  • 当编译器无法自动推断类型或者推断的类型不准确的时候,就需要显示的传入类型参数

泛型约束

介绍

  • 默认情况下,泛型函数的类型变量T可以代表多个类型,这导致无法访问任何属性,比如id("str")调用函数时获取参数长度
function id<T>(num: T): T {
    num.length
    return num
}
const sum = id<number>(10)
const str = id('hello')

image.png

说明

  • T可以是任意类型,无法保证一定存在length属性,比如number就没有length,这个时候泛型就变成了一种负担
  • 此时就需要为泛型添加约束来收缩类型范围

指定更加具体的类型

示例

function id<T>(num: T[]): T[] {
    num.length
    return num
}

说明

  • 既然任意类型没有length属性,那么,我们修改类型为T的数组,那么就可以访问length属性了

extends添加约束

示例

interface ILength {
    length: number
}
function id<T extends ILength>(num: T): T {
    num.length
    return num
}
const arr = id([1, 2, 3])
const str = id('hello')
const obj = id({ name: "张三", length: 1 })

image.png

说明

  • 创建表述约束的接口ILength,该接口要求提供length属性
  • 通过extends关键字使用该接口,为泛型添加约束
  • 该约束表示传入的类型必须具有length属性
  • 上图中 数字类型没有length属性,那么他就会提示报错

多个泛型变量

示例

// 创建一个函数获取对象属性中的值
function getProp<T, K extends keyof T>(obj:T, k:K):T[K]{
    return obj[k]
}
let person = {name:'张三',age:19}
getProp(person, 'name')

说明

  • 泛型的变量可以有多个,并且类型变量之间还可以约束,上边的代码中第二个类型变量受第一个类型变量的约束
  • 添加了第二个类型变量K,俩个类型之间用逗号隔开
  • keyof关键字接受一个对象类型,生成其键名称(可以是字符串或数字)的连个类型
  • 代码中 keyof T实际上获取的是Person对象所有键的联合类型,也就是 'name' | 'age'
  • 类型变量KT约束,也可以这样说 K只能是T所有键中的任意一个

泛型接口

介绍

  • 接口也可以配合泛型使用,用来增加接口的灵活性和复用性 示例
interface IdFunc<T> {
    id: (value: T) => T
    ids: () => T[]
}
let obj1: IdFunc<number> = {
    id(value) { return value },
    ids() { return [1, 2, 3] }
}
obj1.id(1)

说明

  • 在接口名称后面添加<类型变量>,那么这个借口就变成了泛型接口
  • 接口的类型变量,对接口中说有其他成员可见,也就是接口中所有成员变量都可以使用类型变量
  • 使用泛型接口时,需要显示指定具体的类型比如IdFunc<number>
  • 这个时候,id方法的参数和返回值类型都是number,ids方法的返回值类是number[]

泛型类

示例

class GenericNumber<T> {
    defaultValue: T
    add: (x: T, y: T) => T = (x, y) => y
}
const my = new GenericNumber<number>()
my.defaultValue = 10
my.add(1, 2)

说明

  • 泛型类类似泛型接口,在calss名称后面添加<类型变量>,这个类就成了泛型类了

泛型工具类概述

介绍

  • TS内置了一些常见的工具类型,来简化TS中一些常见操作 说明
  • 他们都是基于泛型实现的,并且是内置的,所有可以直接在代码中使用 示例
  • Partial<T>
  • Readonly<T>
  • Pick<T, K>
  • Record<K, T>

泛型工具类Partial<T>

介绍

  • 泛型工具类 Partial<T> 用来构建一个类型,将T的所有属性设置为可选 示例
interface Props{
    path: string
    children: string[]
}
type PartialProps = Partial<Props>

image.png 说明

  • 构造出来的新类型结构和Props相同,但所有属性都变为可选

泛型工具类Required<T>

介绍

  • 泛型工具类Required<T>用来构建一个类型, 将T的所有熟悉设置为必选 实例

interface Props {
    path?:string,
    children?: string[]
}

type RequireProps = Required<Props>

image.png 说明

  • 构造出来的新类型结构和props相同,但所有的属性都变为了必选属性
  • 进入到它的源码,我们看到他是这样定义的

image.png

  • 这个和Partial的源码非常像,只是Partial没有了?前的-,这个-的意思大概是去掉属性的可选参数

泛型工具类 Readonly<T>

介绍

  • 泛型工具类Readonly<T>用来构建一个只读类型,将T的所有属性都设置为readonly只读状态 示例
interface Props{
    path: string
    children: string[]
}

type ReadonlyProps = Readonly<Props>

image.png 说明

  • 构造出来的新类型结构和Props相同,但所有熟悉都变为只读

泛型工具类Pick<T, K>

介绍

  • 泛型工具类 Pick<T, K>T中选择一组属性来构造新类型 示例
interface Props{
    path: string
    title: string
    children: string[]
}

type PickProps = Pick<Props, 'path' | 'title'>

image.png 说明

  • Pick<T, K>工具类有俩个类型变量 T 表示那个接口或者类型的属性,K表示选择那几个属性
  • 其中第二个类型变量,如果只选择一个则只传入T中属性的键名即可
  • 构造出的新类型PickProps,只有pathtitle俩个属性类型

泛型工具类Record<K, T>

介绍

  • 泛型工具类Record<K, T>构造一个对象,属性键为K,属性类型为T 示例
type RecordObj = Record<"a" | "b" | "c", string[]>

image.png

说明

  • Record<K, T>工具类有俩个类型变量 K表示对象有哪些属性; T表示对象属性的类型

索引签名类型

介绍

  • 绝大多数情况下,我们都可以在使用对象前就切丁对象的结构,并为其添加准确的类型 示例

interface AnyObj {
    [k: string]: string
}
let obj11: AnyObj = {
    a: 'a',
    b: 'b'
}

说明

  • 当我们无法确定对象中有哪些属性或者对象中出现任意多个属性时,此时,就用到索引签名类型了
  • 使用[key:string]来约束这个接口中允许出现的属性名称,表示只要是string类型的属性名称,都可以出现在对象中
  • 这样, 对象中就可以出现任意多个属性了

映射类型

介绍

  • 映射类型给予就类型创建新的类型,减少重复代码,提升开发效率 示例一
type PropKeys = "x" | "y" | "z"
type Type2 = {
    [k in PropKeys]: number
}
let type2: Type2 = {
    x: 1, 
    y: 2,
    z: 3
}

说明

  • 映射类型是基于索引签名类型的,所有该语法类似索引签名,也是用了[]
  • key in PropKeys 表示key可以是 Propkeys 联合类型中的任意一个,类似于 forin(let k in obj)
  • 使用映射类型创建的新对象类型 Type2 和类型 Type1 结构完全相同
  • 映射类型只能在类型别名中使用,不能在接口中使用 示例二
type Props3 = {a:number,b:number,c:number}
type Type3 = {
    [k in keyof Props3]: number
}

image.png

写在最后

  • TS系列文章会由浅入深的逐步带你领略TS的魅力
  • 欢迎大家评论,指出不完善的地方