TypeScript完全解读(26课时)_13.TypeScript完全解读-高级类型(2)
13.TypeScript完全解读-高级类型(2)
高级类型中文网的地址:https://typescript.bootcss.com/advanced-types.html
创建文件并在index.ts内引用
this类型
在ts中this也是一种类型
看一个计算器的例子
通过public在constructor上添加属性count类型为number类型,默认值为0
定义方法add,里面把count和传进来的值相加,返回这个实例
实例上调用add方法传入3,输出结果13
接着调用减法的方法,返回11
我们在每个方法后面return this就是这个实例,就可以实现链式调用
创建一个子类PowCounter继承自Counters,并在子类中定义方法pow,这样在调用子类的时候就继承了父类的方法add方法和subtract方法还有属性count
在调用子类的时候,这里通过子类的点.列出了有三个方法可以调用
2的三次方返回8
以为类继承了Counters类,所以可以调用父类的方法add
在调用减法
1.7版本增加this类型后,ts会对返回的this进行判断,会判断this是一个继承来的类里面创建的实例,在调用继承来的方法就不会报错了。这就是this类型的使用
索引类型
包含两个内容,1索引类型查询操作符 2索引访问
索引类型查询操作符
//索引类型查询操作符就是keyof这个操作符,它连接一个类型然后返回由这个类型的所有索引属性名组成的联合类型
上面定义的是接口,然后定义变量infoProp,类型是上面定义的接口
把鼠标放到infoProp上就看到它变成了两个字符串组成的联合类型,要么是name要么是age
相当于这个接口的两个属性名 字符串组成的联合类型
给这个变量赋值name和age的字符串都是可以的。
但是赋值sex就会报错
通过和泛型的结合使用,ts就可以检查使用了动态属性名的代码
keyof T就是获取T里面所有的属性名组成的联合类型,
K extends Keyof T:K的类型只能是T里面的所有属性名组成的联合类型
这里是泛型约束,打错字成了反省了
定义一个函数 getValue 参数Obj类型是T,names参数类型是K的数组。返回值类型这里暂时还没写,写完函数体再去定义
用map遍历names,n就是属性名,函数体内返回obje[n]就是属性名的属性值
调用这个方法,
再传入age的值
返回值类型:T[K],表示T的属性,表示T的属性值组成的数组
保存后,编译器会推荐你 写成这种形式,自动给你改过来
出错的原因是我们的age属性是数值类型
如果age属性也想要的话infoValues的类型就要使用
如果单独这么写:string | number[] 会认为是string或者number类型组成的数组,所以要用括号括包起来:(string | number)[]这样写
保存后编译器会自动给你推荐写成这种形式。这样就不会报错了。
索引访问操作符
就是[]
和我们访问对象的某个属性值是一样的,只不过在ts中,它是用来访问某个属性的类型
比如我们刚才定义的这个接口
定义一个类型别名NameTypes是接口里面的name字段,。NameTypes的类型就相当于是string类型
这就是通过接口的索引访问操作,访问到了name字段它所对应的类型是string,所以这里的类型别名也是string类型
返回类型是:T[K]。T上面的K属性
函数的返回类型
返回的结果是o上面的name
结合接口的例子,key索引的类型为number,值类型为T
定义变量kyes类型为 接口里面所有属性名的类型,因为这里用了keyof
给接口传一个number的类型,鼠标放过来就可以看到keys的类型就是number
讲索引类型的时候讲过,如果索引的类型为number,那实际实现该接口的对象的属性名必须是number类型,但是如果这个接口的索引类型是stirng类型的话,那么实现该接口的对象的属性名设置为数值类型是可以的,
因为数值最后也是会转换为字符串
下面修改接口的索引类型设置为string
那么这个对象的类型就是string | number联合类型,就是上面说的那个原因
也可以使用访问操作符获取索引的类型
keys类型定义的泛型传入的是number类型,获取里接口内索引的类型 也就是传入的T ,就是number类型。这里比较绕。没明白定义objs1的意义。没有用到objs1
先定义接口,里面有很多属性很多类型,下面定义类型别名 Test 。
ketof Type返回的是接口内不为never不为null 不为undefined的属性的属性名
Type实际上就是string、number、object组成的联合类型
映射类型
ts提供了借助旧类型创建新类型的方式就是映射类型。
在映射类型里,新类型以相同的形式去转换旧类型里每个属性
ReadonlyType是一个映射类型
keyof T就是传进来的T的属性名组成的数组,p in 就表示在这个属性数组里进行循环,遍历每一个属性名
前面加上readonly,他就会在前面加上readonly并返回每一个属性名
这里调用映射类型并传入接口 Info1,看起来很像是一个函数,
ReadonlyInfo1就相当于与Info1的接口,只不过他的每个属性前面都有readyonly修饰符
定义一个变量info11类型是ReadonlyInfo1的接口类型的,但是因为这个接口的属性都是只读的,所以如果你想改变属性的值是不可以的
ts内置了Readonly和Partial这两种类型,可以直接就拿来用
Partial每一个都是可选的
还有另外两种映射类型 Pick和Record
鼠标放在pick上,可以看到是哪个类下面定义的
在node_modules/typescript/lib.es5.d.ts
传进来一个对象,再传一个属性列表。最后返回的是部分的属性值
Record的源码,构造属性为K,类型为K组成的类型
官方文档没有补充例子,这里我们来补充例子
返回类型是Pick<T , K>,这就是用的映射类型,返回的这个对象T上的一部分属性名K组成的一个类型
tslint把这个属性关掉
我们开始调用这个方法pick,第一个个参数是我们上面定义的,第二个参数就是info5里面的可以被继承的属性的数组。
我们上面就是那个属性
返回的结果就是name和address组成的联合类型
打印出结果,就是只包含name和address的对象。这就是pick的用法
Record:将对象里面的每个属性转换为其他值的每一个场景
function mapObject<K extends string | number, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U> { const res: any = {} for (const key in obj) { res[key] = f(obj[key]) } return res } const names = { 0: 'hello', 1: 'world', 2: 'bye' } const lengths = mapObject(names, (s) => s.length) // console.log(lengths)
tslint的错误,先关闭
返回了每个属性值对应的长度
把对象中的每个属性,转换为其他的时候,可以赢record
映射类型推断
每一个属性变成了对象,都有get方法和set方法
调用name属性的get方法
调用set方法,给name赋值,再再次打印出来修改后的值
任何对他进行拆包,定义一个函数,在这个函数里进行拆包
打印出了处理之后的对象,有映射类型进行推断,推断原始类型
增加或移除特定修饰符
用+或-
把readonly属性就去掉了。
这样属性就都是可选的了
减去问号
可选属性就都去掉了
keyof和映射类型子在2.9的升级
keyof Objs2就是访问Objs2所有的属性名,
在看一个映射类型的例子,
修改a的属性就会报错了,因为a是一个制度的属性。这就是属性名对number类型和symbol类型的支持
元组和数组上的映射类型
ts3.1会生成新的元组和数组类型,并不会常见一个新的类型
promiseType的参数类型必须是三个Promise分别是Number、String、boolean的类型
unknow
顶级类型
// [1] 任何类型都可以赋值给unknown类型
// [1] 任何类型都可以赋值给unknown类型 let value1: unknown value1 = 'a' value1 = 123
// [2] 如果没有类型断言或基于控制流的类型细化时,unknown不可以赋值给其他类型,此时他只能赋值给unknown和any类型
unkenow赋值给unknow是可以的
// [3] 如果没有类型断言或基于控制流的类型细化时,不能在他上面进行任何操作
对象的类型为unknow类型不能做加法操作
// [4] unknown与任何其他类型组成的交叉类型,最后都等于其他类型
unknow和string的数组最终返回string的数组
// [5] unknown与任何其他类型(除了any是any)组成的联合类型,都等于unknown类型
// [6] never类型是unknown的子类型
这里使用条件类型,这里用到了三元操作符,never extends unknown如果为真 就取的是true 否则取的是false
所以最终返回了true
// [7] keyof unknown 等于类型never
// [8] 只能对unknown进行等或不等操作,不能进行其他操作
// [9] unknown类型的值不能访问他的属性、作为函数调用和作为类创建实例
调用属性值,和把它当方法调用都是不可以的
// [10] 使用映射类型时如果遍历的是unknown类型,则不会映射任何属性
传入unknown类型,返回空的数组
条件类型
ts在2.8引入的,从语法上看像是一个三元操作符
判断传入的 T是否是string类型,如果是就返回string如果不是就返回number类型
传入了字符串a 所以返回的是string类型
传入123返回的就是数字类型了
分布式条件类型
传入一个联合类型
string和number都是any的子类型,所以传入了联合类型就返回了联合类型
官方的示例
传入一个函数类型,返回的就是函数类型了
传入stirng[].就是string组成的数组,都没有符合的就返回了object类型
最终返回object和函数的蓝河类型。因为string[]没有这个选项,所以返回的是object
再来看一个实际的分布式条件类型的应用
type Diff<T, U> = T extends U ? never : T type Test2 = Diff<string | number | boolean, undefined | number>
条件类型和映射类型结合的例子
遍历传入的属性值,如果属性值是函数类型就返回K否则就返回never
修改一下,函数用括号括起来
在后面使用索引访问类型,里面的索引是keyof T
在定义一个接口
tslint提示这里不用用Function,这里需要关闭一下
接口Part有四个字段,updatePart是一个函数类型
K in keyof T:用来遍历T的所有属性名,它的值使用了条件类型:
T[K]:表示当前属性名的属性值,判断它是都为函数类型。如果是反返回当前名的字符串,如果不是就返回never类型。结尾来来使用[ketof T]获取T的属性名,最后通过索引访问类型就是[ketof T]来获取作不never的类型
所以最终调用会返回类型不为never的类型,所以最终返回undatePart函数名
改成两个函数
返回两个函数名组成的两个联合类型
条件类型的类型推断infer
不是用infer的情况
判断T是否是一个数组any[],如果是T[number]:索引访问类型,通过传入一个number索引获取到的其实是它的值的类型。
否则直接返回类型T
如果是数组返回数组元素的类型,不是数组,直接取这个类型
条件为false直接返回T
使用infer的写法
使用infer来修饰U:infer可以推断U里面元素的类型,并且记录在U里面,返回的U就是推断出来的类型
传入string[]返回string类型
传进来的不是数组,
预定义内置的条件类型
在ts2.8的版本增加的
Exclue:从前面这个类型中选在出不在后面这个类型中的类型
// Exclude<T, U> type Type10 = Exclude<'a' | 'b' | 'c', 'a'>
选取T中可以复制给U的类型
// Extract<T, U> type Type11 = Extract<'a' | 'b' | 'c', 'c' | 'b'>
NonNullable,在T中去掉null个undefined
// NonNullable<T> type Type12 = NonNullable<string | number | null | undefined>
ReturnType
获取函数类型返回值类型
// ReturnType<T> type Type13 = ReturnType<() => string> type Type14 = ReturnType<() => void>
没有返回类型的函数
InstanceType<T>:获取构造函数的实例类型
不好理解,先看下库文件里面的实现
T必须是一个构造函数类型
返回值无所谓,any类型
条件判断,判断T是否是构造函数类型
用infer推断出R的类型
传入类型A
返回的类型也是A
传入上面定义的AClass,那么类型就是AClass。这里为什么要用typeof AClass呢,因为Aclass是一个值,不是一个类型,要获取它的类型就必须用typeof
传入any类型就是any
传入never返回的就是never类型
传入stirng类型报错,string不满足构造函数类型
再来理解下源码:
T是否是构造函数,推断出T的返回值
是构造函数,或者构造函数的子类型,就返回构造函数推断出来的返回值类型R,也就是构造函数返回类型。
如果不是就返回一个any