泛型 Generics---typeScript中最难的一部分
Adding simple type annotations //给我们的变量加一些简单的类型声明,
string类型/number类型/any类型
//泛型是怎么出现的,它要解决什么的问题?
//function echo(arg){
// return arg
// }
//调用
//const result = echo(123)
//传入数字123,返回any类型,
//我们的变量丧失了类型,
//这样写就没啥问题了
function echo(arg: number): number{
return arg
}
const result = echo(123)
//但是我们可能传入其他类型,
//我们传入和返回的没办法统一,
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,
而在使用的时候再指定类型的一种特性。
function echo<T>(arg: T): T{
return arg
}
const str: string = 'str'
const result = echo(123)
//传入类型,返回的也是string
//我们也可以完全不指定,这时候类型推论会帮我们做事情,
function echo<T>(arg: T): T{
return arg
}
const result = echo('str')
//类型推论会推断,str就是string类型,自然它返回的也是一个string类型,
泛型可以传入多个值,
新需求:我们有一个 tuple元组,里面有2个值,他们的类型都是随意的,
这时候我要返回一个新的tuple ,调换他们的位置,
//新建一个函数
function swap(tuple){
return [tuple[1],tuple[0]]
}
//当然会丧失他们的type,
//创建泛型,
function swap<T,U>(tuple: [T,U]): [U,T]{
return [tuple[1],tuple[0]]
}
const result2 = swap(['string',123])
//鼠标移至result2,显示const result2: [number,string],
//已经出现了它的类型,
//第0项我们可以把它当作number使用,result2[0].调用number上边的方法
//第1项我们可以把它当作string使用,result2[1].调用string上边的方法
本节总结
介绍了泛型的动机和它最简单的用法,
泛型,它就像一个占位符,或者是一个变量,
在使用的时候我们可以把定义好的类型像参数一样传入,
然后它可以原封不断的帮我们输出,
泛型 Generics---约束泛型
//上节学习了泛型出现的动机要解决什么问题和简单用法
//我们可以把它看成一个占位符,在使用的时候才动态的填入确定的类型值,
//约束泛型
function echoWithArr<T>(arg: T): T{
console.log(arg.length) //发现报错,Property 'length' does not exist on type 'T'
return arg //这个例子中,泛型T不一定包含属性length
}
因为在我们函数内部使用泛型变量的时候,由于事先不知道它是什么类型,
所以不能随意的来操作它的属性和方法,
//我们就决定我们这个函数应该是作用给一个含有T类型的Array,
//这样length这个属性也就有了
//T[]
function echoWithArr<T>(arg: T[]): T[] {
console.log(arg.length)
return arg
}
//const arrs = echoWithArr([1,2,3]),
//鼠标移至arrs,显示const arrs: number[]
//但是这个解决方案不是完美的,我们只能传入数组
//但是可能对象,甚至简单类型string都可以有length这个属性,
//const arrs = echoWithArr('str') //会报错
我们需要一个新的解决方案,我们可以对泛型进行一个约束,
只允许函数传入那些包含length属性的变量,这就是约束泛型,
interface IWithLength {
length: number;
}
function echoWithLength<T extends IWithLength>(arg: T): T {
console.log(arg.length)
return arg
}
用extends关键字来约束传入的泛型,
告诉它,你必须要有length这个属性,要不然会报错,
//const str = echoWithLength('str') //不会报错
//鼠标移至str,显示const str:'str'
//const obj = echoWithLength({length: 10})
//鼠标移至obj,显示const obj:{length: number}
//const arr2 = echoWithLength([1,2,3])
//鼠标移至obj,显示const arr2: number[]
//interface--Duck Typing
只要你叫起来像鸭子,只要你有length属性,那么就可以符合这个约束,
就没有问题,不管你是什么样的类型都可以,
不管你是字符串,object,Array都没有问题,
本节总结:
着重讲了约束泛型,关键是在泛型中使用extends关键字,就可以让
传入值满足我们特定的约束条件,而不是想传入什么就传入什么,
泛型 Generics---泛型在类和接口中的使用
//之前我们显示的泛型都是作用在函数中,在函数的参数和返回值中使用泛型,
//泛型在类上边应用,
我们创建一个对列类,
队列中有两个方法,一个是进入对列push(),二是离开对列pop()
对列是个先进先出的数据结构,所以我们使用了push()和shift()两个方法,
这时候我们就可以使用这个对列做一些事情,
class Queue {
private data = []
push(item){
return this.data.push(item)
}
pop(item){
return this.data.shift()
}
}
// const queue = new Queue() //创建一个对列
// queue.push(1)
//queue.push('str')
//console.log(queue.pop().toFixed())
//console.log(queue.pop().toFixed())
//此时运行会报错
//queue.pop(...)toFixed is not a function
//为什么在我们的ts代码中没有抓到这个错误呢
//首先它允许你像对列中添加任何类型的数据,
//当然数据被弹出对列的时候也可以是任意类型,
//我们可以看到我们可以在里面添加string类型的数据,
//使用的时候就会出现无法捕捉的错误,
//比如这个例子,我们弹出的第二个类型是string类型,但我们调用了
//只有数字类型才有的方法,
//所以在typeScript中没有抓到这个错误,
//实际上该用法是假如只有number类型时才会被添加到对列里,
一个解决办法是添加的时候创建一个约束,
// push(item: number){
// return this.data.push(item)
// }
//queue.push('str') //此时会出现一个错误
//这是一个很坏的解决方法
假如当你想创建一个字符串的对列的时候,将不得不再次修改相当大类型的代码,
我们真正想要的是一种方法是无论什么类型被推入对列,被推出的类型都与推入的是一样的,
这时候就可以让泛型来帮助我们,我们可以创建一个泛型类,怎么写?
之前我们是在函数名称后面加<>,现在我们在类名称后面加<>,
class Queue<T>{
private data = []
push(item: T){ //希望被push进去的类型是T
return this.data.push(item)
}
pop(): T{ //被推出的类型应该是T
return this.data.shift()
}
}
//这时候我们创建了一个带有泛型的对列,一个类,
要初始化的时候,我们需要在Queue这个构造函数后面加上你想要的类型,
const queue = new Queue<number>()
//我们上边说类就可以用泛型来描述,
//interface--接口也可以接受泛型的洗礼,变得灵活起来,
泛型和interface,
定义一个interface叫KeyPair,它有两个值,一个叫key,一个叫value
我们希望key和value这两个值都是我们在使用的时候,动态的的传入,
现在我不确定它是什么类型,
所以皆可以给KeyPair定义两个类型,
interface KeyPair<T,U>{
key: T;
value: U;
}
//let kp1: KeyPair<number,string> = {key:1, value:'str'}
//let kp2: KeyPair<string,number> = {key:'str', value:123}
//定义数组类型的时候,
//let arr: number[] = [1,2,3]
//现在我们可以使用表示泛型的形式来表示,
//let arrTwo: Array<number> = [1,2,3]
//以上就是interface搭配泛型以后可以灵活地返回不同的类型,
关于泛型的总结:
1.创建一个拥有特定类型的容器,比如类和interface上的泛型,
仿佛给一个容器贴标签一样,
let arrTwo: Array<number> = [1,2,3]
给Array贴上了number标签,告诉arrTwo我要你是一个装满number类型的数组,
const queue = new Queue<number>()
或者告诉一个类我希望你是一个装着数字的对列,
甚至你可以想它像一个可变的参数那样,在用的时候传入,生成一个不同类型的
一个容器,
2.可以用它来灵活的约束参数的类型,不需要参数是个特别死板的类型,
比如说,我们不希望它是一个特定string类型,不希望它是一个number类型,
而我要传入的参数必须有某某属性,某某方法,否在就会报错,
//(第一节:generrics,ts)
3.在函数使用的时候,函数的类型推断不会流入到函数体内,
所以使用表达式没法明确建立类型的绑定,
用泛型可以让我们打破这个鸿沟,
function echo<T>(arg: T): T{
return arg
}
const result = echo(true) //result就会返回它传入的类型,
网友评论