这是我参与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')
说明
- 语法:在函数名称后面添加
<>尖括号,尖括号中指定具体的类型,比如,number和string - 当传入
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')
说明
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 })
说明
- 创建表述约束的接口
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' - 类型变量
K受T约束,也可以这样说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>
说明
- 构造出来的新类型结构和
Props相同,但所有属性都变为可选
泛型工具类Required<T>
介绍
- 泛型工具类
Required<T>用来构建一个类型, 将T的所有熟悉设置为必选 实例
interface Props {
path?:string,
children?: string[]
}
type RequireProps = Required<Props>
说明
- 构造出来的新类型结构和
props相同,但所有的属性都变为了必选属性 - 进入到它的源码,我们看到他是这样定义的
- 这个和
Partial的源码非常像,只是Partial没有了?前的-,这个-的意思大概是去掉属性的可选参数
泛型工具类 Readonly<T>
介绍
- 泛型工具类
Readonly<T>用来构建一个只读类型,将T的所有属性都设置为readonly只读状态 示例
interface Props{
path: string
children: string[]
}
type ReadonlyProps = Readonly<Props>
说明
- 构造出来的新类型结构和
Props相同,但所有熟悉都变为只读
泛型工具类Pick<T, K>
介绍
- 泛型工具类
Pick<T, K>从T中选择一组属性来构造新类型 示例
interface Props{
path: string
title: string
children: string[]
}
type PickProps = Pick<Props, 'path' | 'title'>
说明
Pick<T, K>工具类有俩个类型变量T表示那个接口或者类型的属性,K表示选择那几个属性- 其中第二个类型变量,如果只选择一个则只传入
T中属性的键名即可 - 构造出的新类型
PickProps,只有path和title俩个属性类型
泛型工具类Record<K, T>
介绍
- 泛型工具类
Record<K, T>构造一个对象,属性键为K,属性类型为T示例
type RecordObj = Record<"a" | "b" | "c", string[]>
说明
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
}
写在最后
- TS系列文章会由浅入深的逐步带你领略TS的魅力
- 欢迎大家评论,指出不完善的地方