Typescript学习(七)泛型的应用
类型工具中的泛型
前面我们介绍了许多类型工具, 比如: 类型别名、索引类型、映射类型 等等; 而泛型往往会和他们组合在一起, 形成一些工具类型! 所谓的工具类型, 其实就像我们日常Javascript开发中的util.js中的一些工具方法, 它们往往是一些为了应对特定场景而封装好的方法, 在Typescript中, 工具类型就是这种方法, 而泛型, 就可以看作是这些方法的入參!
// 定义了一个工具类型AllBeString
type AllBeString<T> = {
[P in keyof T]:string;
}
interface Person {
name:string;
age:number;
height: number;
weight: number;
}
// 将Person类型中的所有成员转为string类型
type NewPerson = AllBeString<Person>
/**
*
* type NewPerson = {
* name: string;
* age: string;
* height: string;
* weight: string;
*}
*/
以上案例中, AllBeString就像是一个函数, 它接受一个参数T, 在其内部逻辑中, 利用类型工具中的映射类型, 将T中的每个属性的类型变为string类型
除此之外, 还有一种被称为条件类型的类型工具, 能和类型别名能组成很多拥有非常有用的功能的工具类型, 先来看一个简单的案例, 了解它的基本特点:
type IsString<T> = T extends string ? true : false
type isStrType1 = IsString<123> // false
type isStrType2 = IsString<'hello'> // true
在上面的IsString这个类型工具中, 我们通过extends关键字来判断传入的泛型T是否属于string类型, 并以此为条件返回不同的结果, 这种就是条件类型, 条件类型主要用于限制约束泛型的类型, 使工具类型具有较强的灵活性;
在Typescript中, 其实有很多已经内置好了的的工具类型, 我们直接拿来用就行了, 它们种类繁多, 能适用于很多不同的场景, 但是它们的基本原理, 都是一样的, 都是利用类型工具的组合来实现的其逻辑:
type NonNullable<T> = T & {};
type K = NonNullable<string | number | null | undefined | boolean>
// 类型推导结果: type K = string | number | boolean
NonNullable为内置工具类型, 其组成非常简单, 即 类型别名 + 一个交叉类型; 这个工具类型作用是排除掉null和undefine类型; 由于在Typescript中, 任意类型和空对象组成的交叉类型, 都是这个类型本身, 但是, 推导过程中, 又会忽略掉null,undefined两个类型, 因此这个工具类型利用了此原理, 实现了剔除null和undefined的功能
还有非常常见的Record工具类型, 我们可以利用它来快速定义一个对象的类型, 它由类型别名+条件类型+索引类型查询 + 映射类型组成:
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type Result = Record<string|number, string>
/**
* type Result = {
* [x: string]: string;
* [x: number]: string;
*}
*/
我们知道, keyof作为一个索引类型查询操作符, 可以获取一个类型的所有属性名或者一个索引类型的所有键名, keyof any则是获取所有可作为属性名/键名的类型组成的联合类型(string|number|symbol); 而条件类型在这里的作用就是规定泛型K, 必须符合string|number|symbol这个联合类型! 而在后续的映射类型中, 将K中的每一个成员都映射出来, 作为新对象的键/属性的类型;
默认泛型
前面我们介绍了泛型的基本使用, 我们知道, 泛型就像是一个函数的参数, 而函数的参数是由默认值的, 比如:
function firstCode (params = 'world') {
return 'hello ' + params
}
既然如此, 泛型, 也应该拥有默认值
type MoreType<T = string> = T | number | symbol
type newType = MoreType // string | number | symbol
这样, MoreType已经有了默认的泛型, 后续使用的时候, 我们可以不用写明泛型了, 只需要一个工具类型即可
泛型约束条件
前面介绍过了条件类型这个类型工具, 我们知道, 它能够很好地约束泛型的类型, 如果泛型不符合某种条件(即 不是某个类型的子类型), 那Typescript将不执行对应的代码! A extends B, 即 A为B的子类型, 所谓子类型, 可以从2个角度理解: 更精确 或者 更多属性; 更精确, 通常指字面量类型比原始类型更精确, 如: 'hello world' extends string 成立, 即 字面量类型为原始类型子类型; 更多属性, 指的是子类除了拥有基类的属性, 还会具有自己的属性, 如: {name: string} extends {}, 具有具体属性的对象类型自然是空对象的子类型; 再来看几个案例
// 约束T必须是200 | 201 | 304的子类, 否则, 返回错误
type isSuccess<T> = T extends 200 | 201 | 304 ? 'success' : 'fail'
type myStatus = isSuccess<404> // fail
再来看个稍微复杂点的
class Animal {
constructor (public color:string, age:number) {}
}
class Dog extends Animal {
bite () {
console.log('dog will bite you')
}
}
class Car {
run () {
console.log('I can run')
}
}
type isAnimal<T> = T extends Animal ? 'yes' : 'no'
type dogResult = isAnimal<Dog> // 'yes'
type carResult = isAnimal<Car> // 'no'
多泛型关联
一个工具类型可以接受多个泛型, 正如一个函数可以接受多个参数一样; 所以在Typescript中, 我们还能通过关联多个泛型, 使他们存在某种关联, 进而达到约束它们类型增强代码灵活性的目的:
function getProperty<K extends keyof T, T> (value:T, key: K):T[K] {
return value[key]
}
getProperty({name: 'jack', age: 18}, 'name')
在上面的案例中, 我们通过条件类型来明确了K和T之间的关系, 即K其实是T的键的类型, 因此我们在实际调用函数过程中, key必须是value的属性成员! 所以, 当我们传了第一个参数{name: 'jack', age: 18}之后, 第二个参数只能在'name'和'age'两个值之间选择, 而不能随意输入任何值, 这就是泛型建立关联后产生的约束; 还有一个前后关联的典型场景就是选择省市区:
function getAddress<T, P extends keyof T, C extends keyof T[P], A extends keyof T[P][C]> (obj: T, province:P, city: C, area:A) {
console.log(`${province as string}省${city as string}市${area as string}区`)
}
let addressObj = {
'广东省': {
'广州市': {
'天河区': 'tianhe',
'荔湾区': 'liwan'
},
'佛山市': {
'顺德区': 'shunde',
'南海区': 'nanhai'
}
}
}
getAddress(addressObj, '广东省', '佛山市', '南海区')
我们可以在泛型层面直接限定地理数据、 省、市、区三者的关系, 从而在我们调用getAddress的时候, 其参数的类型能够根据前面的参数实时改变, 增加了代码的灵活性;
对象中的泛型
我们在定义对象类型的时候, 同样可以使用到泛型, 前面我们都是使用类型别名举例子, 这里使用接口interface来实现下对象中使用泛型的场景
interface Person<T> {
name:string;
age:number;
skill: T
}
let developer:Person<'code'|'debug'> = {
name: '小明',
age: 35,
skill: 'code'
}
let soldiers:Person<'shoot'|'fire'|'run'>
let yezhichao:Person<'run'> = {
name: '叶志超',
age: 18,
skill: 'run'
}
当然泛型还可以进行嵌套
type P = Promise<Person<'run'>>
function getTaobingInfo (params:Person<'run'>):P {
return new Promise((resolve) => {
resolve(params)
})
}
getTaobingInfo(yezhichao).then(res=> {console.log(res)})
函数中的泛型
不仅对象中存在类型, 作为Javascript中非常重要的一份子, 函数自然也不能落后! 函数的泛型, 其实在前面的多泛型关联的案例中已经使用过了, 即前面的 getProperty以及getAddress方法, 函数中使用泛型的基本格式就是
function fn<T> (input:T):T {}
当然, 不是说返回值必须和参数类型一致, 这里只是展示函数中的泛型出现的位置, 即泛型的消费方, 很容易看出, 函数泛型的消费方, 就是参数和返回值; 此处就不再赘述案例了
类中的泛型
类中的泛型需要注意的是, 函数中的泛型消费方是参数/返回值, 而类中的消费方则是属性/方法
class Animal<T> {
constructor(public name:string) {}
public foods:T[] = []
eat(food:T) {
console.log(this.name + 'like' + food)
}
}
const dog = new Animal<'bone'|'meat'>('dog')
dog.eat('bone')
dog.foods.push('bone')
dog.foods.push('meat')
转载自:https://juejin.cn/post/7267733291570528295