在了解Ts的逆变和协变之前,我们先了解一下什么叫做鸭子类型
鸭子类型
如果一只鸟走路像鸭子 ,游泳也像,做什么都像,那么这只鸟就可以成为鸭子类型。
我们不用关心鸭子的定义是什么,只要符合我们通常意义上的认知,那么他就是这个物体。
在 TypeScript中,只要对象符合定义的类型约束,那么我们就可以视为他是。
协变
协变,又可以称为鸭子类型
interface A { name:string age:number } interface B { name:string age:number sex:string } let a:A = { name:"老墨我想吃鱼了", age:33, } let b:B = { name:"老墨我不想吃鱼", age:33, sex:"女" } a = b
接口A中有name和age属性,接口B有name,age和sex属性。 当我们将b赋值给a的时候,因为B类型是A类型的子集,所以我们可以将B赋值给A
回到我们的鸭子类型上,我们可以将A理解成那个鸭子,B理解成那只鸟。因为B中的name像A,B中的age像A,即B满足A的所有特性,所以B就是A类型,可以正确赋值。换句大白话讲,就是B类型只能多,不能少,才可以正确赋值。
我们再从集合的角度来理解协变。刚刚我们提到B是A的子集,相信一定会有同学出来反驳,明明,B的属性比A多,A才是B的子集!
这里的集合概念和我们平时认知的不太一样。大家可以换个角度想,接口中的属性可以相当于我们的限制条件。就拿我们找工作举例子,熟悉React的应聘者是一个集合A,当我们增加我们的招聘要求时,不仅熟悉React,还要熟悉node,满足这两个招聘条件的应聘者又构成一个集合B。那么集合B是不是集合A的子集呢?
相信大家对协变已经有自己的理解了吧!
逆变
逆变一般发生于函数的参数上面
interface A { name:string age:number } interface B { name:string age:number sex:string } let a:A = { name:"老墨我想吃鱼了", age:33, } let b:B = { name:"老墨我不想吃鱼", age:33, sex:"女" } a = b let fna = (params:A) => { } let fnb = (params:B) => { } fna = fnb //错误 fnb = fna //正确
我们刚刚说到B是A的子集, 但是我们在函数的参数中使用A/B类型,为什么和刚刚的结果不一样呢? 只能将fna赋值给fnb
小栗子又来了
我们在使用函数进行赋值的时候,fnb = fna
。当我们赋完值调用fnb时,实际上调用的是fna
let a = () => { console.log('a') } let b = () => { console.log(b) } b = a; b() //a
所以fna中params:A又充当了我们的主类型,B是我们的子类型,B是A的子集,所以赋值成功!
双向协变
tsconfig中strictFunctionTypes 设置为false 支持双向协变 fna fnb 随便可以来回赋值,但是不推荐这么设置。
通过tsc --init
生成配置项
fna = fnb fnb = fna