泛型:
泛型是可以在保证类型安全的前提下,让函数等与多种类型一起工作,从而实现复用,常用于函数,接口,class中
创建泛型函数:
function id<Type>(value: Type): Type {
return value
}
(1)语法:在函数名称后面添加<>(尖括号),尖括号添加类型变量,比如此处的Type
(2)类型变量Type,是一种特殊类型的变量,它处理类型而不是值
(3)该类型变量相当于一个容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数的时候指定)
(4)因为Type是类型,因此可以将其作为函数参数的返回值类型,表示参数和返回值具有相同的类型
(5)类型变量是Type,可以是任意合法的变量名称
调用泛型函数:
function id<Type>(value: Type): Type {
return value
}
// 调用
const num = id<number>(10)
const str = id<string>('hello')
语法:在函数名称后面添加<>(尖括号),尖括号指定的具体类型,比如此处的number或string,
这样,通过泛型就做到了让id函数与多种不同类型一起工作,实现了复用的同时保证了类型安全
简化调用泛型函数:
(1)在调用泛型函数的时候,可以省略<类型>来简化泛型函数的调用
(2)此时,TS内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出变量Type的类型
推荐:使用这种简化的方式调用泛型函数,使代码更短,更易于阅读
但是,当编译器无法推断类型或者推断的类型不准确时,就需要显示的传入参数类型
泛型约束:
默认情况下,泛型函数的类型变量Type可以代表多个类型,这导致无法访问任何属性
比如:下面这个代码获取参数的长度:
function id<Type>(value: Type): Type {
// value.length会报错
console.log(value.length)
return value
}
报错是因为Type代表的是任意类型,无法保证一定存在length属性,比如number类型就没有length;
所以,此时,就需要为泛型添加约束来收缩类型(缩窄类型的取值范围)。
添加类型约束收缩类型,主要有两种方式:
1、指定更加具体的内容
interface ILength {length: number}
function id<Type>(value: Type[]): Type[] {
console.log(value.length)
return value
}
上面这个代码就是将类型修改为Type[] (Type类型的数组),因为只要是数组就有length属性,就可以访问了
2、添加约束
interface ILength {length: number}
function id<Type extends ILength>(value: Type): Type {
console.log(value.length)
return value
}
(1)创建描述约束的接口ILength,该接口提供length属性
(2)通过extends关键字使用该接口,为泛型(类型变量)添加约束(这里的extends不是代表继承,而是约束)
(3)该约束表示:传入的类型必须具有length属性
注意:传入的实参(比如数组)只要有length属性即可,这也符合前面讲到的接口的类型兼容
泛型类型可以有多个,并且类型变量之间还可以约束:(比如:第二个类型变量受第一个类型变量的约束)
比如:创建一个函数来获取对象中属性的值
function getProps<Type, Key extends keyof Type>(obj: Type, key: Key): {
return obj[key]
}
let person = {name: 'jack', age: 18}
getProps(person, 'name')
getProps(person, 'age')
(1)添加了第二个类型变量Key,两个类型变量之间使用,(逗号)分隔
(2)keyof关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
(3)这个例子中,keyof Type实际上获取的是person对象所有的键的联合类型,也就是 'name' | 'age'
(4)类型变量Key受Type约束,可以理解为: key只能是Type所有键中的任意一个,或者说只能访问对象中存在的属性
泛型接口:
接口可以配合泛型来使用,以增加其灵活性,增强其复用性
// 泛型接口
interface IdFunc<Type> {
id: (value: Type) => Type
ids: () => Type[]
}
let obj: IdFunc<number> = {
id(value){return value},
ids(){return [1,2,4]}
}
(1)在接口名称的后面添加<类型变量>,那么,这个接口就变成了泛型接口
(2)接口的类型变量,对接口中所有其他成员可见,也就是接口中所有成员都可以使用类型变量
(3)使用泛型接口时,需要显示指定具体的内容
(4)上面的例子中,id方法的参数和返回值类型都是number,ids方法的返回值类型是number[]
实际上,JS中的数组在TS中就是一个泛型接口
因为当我们在使用数组时,TS会根据数组的不同类型,来自动的将类型变量设置为相应的类型
技巧:可以通过Ctrl+鼠标左键来查看具体的类型信息
泛型类:
// 泛型类
class GenericNumber<NumType> {
defaultValue: NumType
add: (x: NumType, y: NumType) => NumType
}
// 当其构造函数里指定了的话
// 下面那个new GenericNumber<number>()可以不用<number>
const myNum = new GenericNumber<number>()
muyum.defaultValue = 10
// 就比如
class GenericNumber1<NumType> {
defaultValue: NumType
add: (x: NumType, y: NumType) => NumType
constructor(value: NumType){
this.defaultValue = value
}
}
// 此时 可以省略类型变量不写
const myNum1 = new GenericNumber1(100)
myNum1.defaultValue = 10
泛型工具类:
TS内置了一些常用的工具类型,来简化TS中一些常见操作
它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且都是内置的,可以直接在代码中使用,有很多的类型,主要学习:
1、Partial<Type> 2、Readonly<Type> 3、Pick<Type> 4、Record<Type>
1、Partial<Type>用来构造(创建)一个类型,将Type的所有属性设置为可选(并不会改变传入的类型本身)
interface Props {
id: string
children: number[]
}
const ParticalProps = Partial<Porps>
构造出来的新类型ParticalProps结构与Props相同,但所有属性都变为可选的
2、Readonly<Type>用来构造一个类型,将Type的所有属性都设置为readonly(只读)
interface Props {
id: string
children: number[]
}
type ReadonlyProps = Readonly<Props>
let props: ReadonlyProps = {
id: '1',
children: []
}
// 下面这一行代码会报错,因为是只读的不能改
props.id = '2'
3、Pick<Type, Keys>从Type中选择一组属性来构造新类型
(1)有两个类型变量,Type是表示选择谁的属性,Keys表示选择哪几个属性
(2)其中第二个类型变量,如果只选一个则只传入该属性名即可
(3)第二个类型变量传入的属性只能是第一个类型变量中存在的属性
interface Props {
id: string
title: string
children: number[]
}
type PickProps = Pick<Props, 'id' | 'title'>
// 构造出来的PickProps只有id和title两个类型属性
4、Record<Keys, Type>构造一个类型对象,属性键为Keys,属性类型为Type
第一个参数表示对象有哪些属性,第二个参数表示对象属性的类型
type RecordObj = Record<'a' | 'b' | 'c', string[]>
let obj: RecordObj = {
a: ['a'], b: ['b'], c: ['c']
}
索引签名类型:
其实绝大多数情况下,我们都可以在使用对象前就确定对象的结构,并为对象添加准确的类型
但是:当无法确定对象中有哪些属性(或者说对象中可以出现任意多属性)就要用到索引签名类型
// 索引签名类型
interface AnyObject {
[key: string]: number
}
let obj: AnyObject = {
a: 1
b: 2
}
(1)使用[key: string]来约束该接口中允许出现的属性名称,表示只要是string类型的属性名称,都可以出现在对象中
(2)这样,对象obj中就可以出现任意多个属性(比如:a,b等)
(3)key只是一个占位符,可以换成任意合法的变量名称
(4)前置知识:JS中对象({})的键是string类型的
在JS中,数组是一类特殊的对象,特殊在数组的键(索引)是数值类型
并且数组中可以出现任意多元素。所以,在数组对应的泛型接口中,也用到了索引类型签名
模拟数组类型:
// 模拟原生的数组接口,并使用 [n: number] 来作为索引类型签名
interface myArray<T> {
// 只要是number类型的键(索引)都可以出现在数组中
// 或者说数组中可以有任意多元素
[n: number]: T
}
let arr: myArray<number> = [1, 3, 5]
映射类型:
基于旧类型创建新类型(对象类型),减少重复,提升开发效率
比如:类型PropKeys有x,y,z,另一个类型Type1中也有x,y,z,并且Type1中的x,y,z,类型相同:
type PropKeys = 'x' | 'y' | 'z'
type Type1 = {x: number; y: number; z: number}
// 这样写没有问题,但是x,y,z,重复写了两次
这种情况就可以使用映射类型来进行简化:
type Props = 'x' | 'y' | 'z'
tyep Type2 = {[key in Props]: number}
key in Props表示key可以是Props联合类型中的任意一个,类似于forin(let key in obj)
使用映射类型创建的新对象类型(Type)和类型Type1结构完全相同
映射类型只能在类型别名中使用,不能在接口中使用
映射类型除了根据联合类型创建新类型外,还可以根据对象类型来创建
type Props = {a: number; b: number; c: boolean}
type Type3 = {[key in keyof Props]: number}
(1)首先,先执行keyof Props获取到对象类型Props中所有的键的联合类型 'a' | 'b' | 'c'
(2)然后,key in ... 就表示key可以是Props中所有的键名称中的任意一个
索引查询类型:
T[P] 的语法 ---> 用来查询属性的类型
type Props = {a: number; b: string; c: boolean}
type TypeA = Props['a']
// 一定要写type TypeA,不然会报错
// 鼠标放在TypeA上面会显示type TypeA = number
Props['a']表示查询类型Props中属性'a'对应的类型number,所以TypeA的类型为number
注意:[ ]中的属性必须存放在于被查询类型中,否则就会报错
同时查询多个索引类型:
type Props = {a: number; b: string; c: boolean}
type TypeA = Props['a' | 'b'] // string | number
type TypeB = Props[keyof Props] // string | number | boolean
使用keyof操作符获取Props中所有键对应的类型