最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • TypeScript 类型系统 协变与逆变的理解 函数类型的问题

    正文概述 掘金(Nctdt)   2020-12-13   541

    前言

    TypeScript 中有许多关于类型系统的概念,如果只知其一不知其二的话,那么就有可能被报错打的满地找牙。

    这篇文章写的是关于类型系统中的协变与逆变的概念,了解协变和逆变是如何发生及运作的。

    类型关系

    理解一个新东西所需要的是一个良好且完善的上下文,所以需要先了解最基础的类型关系

    在 TypeScript 中的类型只与值有关,即鸭子类型。

    父子类型

    普通类型

    假设有如下接口类型:

    interface Animal {
      age: number
    }
    
    interface Dog extends Animal {
      bark(): void
    }
    

    Dog 继承于父类 Animal ,也就是说 DogAnimal 的子类型,我们可以称之为 Dog ≼ Animal

    可以看到,子类相较于父类更具体,属性或行为更多。

    同时可以看到因为鸭子类型而出现的一个现象(同时也被称为类型兼容性)。

    let animal: Animal
    let dog: Dog
    
    animal = dog 
    // √ 因为 animal 只需要 age 一个属性,而 dog 中含有 age 和 bark() 两个属性,赋值给 animal 完全没问题。
    dog = animal 
    // × Error: Property 'bark' is missing in type 'Animal' but required in type 'Dog'.
    

    因为 animal 中缺少 dog 需要的 bark() 属性,因此赋值失败并报错。

    总结

    1. 子类型比父类型描述的更具体,父类型相对于子类型是更广泛的,子类型相对于父类型是更精确的。
    2. 判断是否是子类型可以这么理解,子类型是一定可以赋值给父类型的。

    联合类型

    假设有如下类型:

    type Parent = 'a' | 'b' | 'c'
    type Son = 'a' | 'b'
    
    let parent: Parent
    let son: Son
    
    son = parent 
    // × Error: Type 'Parent' is not assignable to type 'Son'.
    // Type '"c"' is not assignable to type 'Son'.
    parent = son 
    // √ 
    

    Parent 可能是 'c' 但是 Son 类型并不包括 'c' 这个字面量类型,因此赋值失败并报错。

    可以从这个案例看出 Son ≼ Parent 。因为 Parent广泛Son具体

    可以这么理解:联合类型相当于集合,Son就是Prent子集。不过在这还是说SonParent的子类型。

    协变和逆变

    维基百科定义

    依旧假设我们有依旧有上面的AnimalDog两个父子类型。

    协变(Covariance)

    协变的情况其实很简单就是上面说的类型兼容性,因此协变其实无处不在。

    let animals: Animal[]
    let dogs: Dog[]
    
    animals = dogs
    

    完全没问题,原因之前说了,就不再重复了。这就是协变现象。

    逆变(Contravariance)

    逆变现象只会在函数类型中的函数参数上出现。 假设有如下代码:

    let haveAnimal = (animal: Animal) => {
      animal.age
    }
    let haveDog = (dog: Dog) => {
      dog.age
      dog.bark()
    }
    
    haveAnimal = haveDog 
    // Error: Type '(dog: Dog) => void' is not assignable to type '(animal: Animal) => void'.
    //   Types of parameters 'dog' and 'animal' are incompatible.
    //     Property 'bark' is missing in type 'Animal' but required in type 'Dog'.
    
    haveAnimal({
      age: 123,
    })
    

    传入的 Animal 没有 haveAnimal 需要的 bark() 属性,因此在检查时报错了。

    注意:TS之前的函数参数是双向协变的,也就是说既是协变又是逆变的、且这段代码并不会报错。但是在如今的版本 (Version 4.1.2)tsconfig.json 中有 strictFunctionTypes 这个配置来修复这个问题。(默认开启)

    那么这时候修改代码为:

    - haveAnimal = haveDog
    + haveDog = haveAnimal
    

    发现完全没问题!

    因为我们在运行 haveDog(实际运行还是 haveAnimal ) 的时候会传入 Animal 的子类Dog,之前说过子类型的属性比父类型更多,因此haveDog需要访问的属性在 Animal 中都有,那么在 Dog 类型中肯定只会更多。

    可以发现对于两个父子类型作为函数参数构建两个函数类型,这两个的函数类型的父子关系逆转了,这就是逆变

    同时,在返回值类型上和平常没什么区别是协变的。(感兴趣的可以自己试试)

    总结:在函数类型中,参数类型是逆变的,返回值类型是协变的。

    练习

    有如下代码:

    type NoOrStr = number | string
    type No = number
    let noOrStr = (a: NoOrStr) => {}
    let no = (a: No) => {}
    

    noOrStr = no 会报错还是 no = noOrStr 会报错。

    可以思考一下,谁是父类谁是子类然后在进行逆变转换。

    练习答案

    noOrStr = no 会报错。

    解析

    • 在练习中,可以看做 No ≼ NoOrStr ,进行逆变转换: noOrStr ≼ no 。子类可以赋值给父类,父类不能赋值给子类,因此 no = noOrStr 是对的没问题,noOrStr = no 就会报错。
    • 又或者换种角度,noOrStr 能处理 number | string 类型的值,而 no 只能处理 number 类型的值。
      • 因此当 no = noOrStr 时没问题,因为调用 no() 时只会传入 number 类型的值,而 noOrStr 可以处理包括 number 两种类型的值。
      • 而当 noOrStr = no 时就出问题了,因为调用 noOrStr() 时会传入 number | string 类型,而 no 只能处理 number 类型的值,当调用 noOrStr() 传入 string 类型的值时, no 处理不了,因此报错。

    结语

    这篇文章的感悟是我学习 TS 途中遇到的一个问题查询资料并理解后所诞生的。如果有错误或疏漏欢迎指出:)

    同时扩展下:infer 在协变和逆变的情况下是有不同现象的,具体可查看文档(我忘记在哪里了)。


    下载网 » TypeScript 类型系统 协变与逆变的理解 函数类型的问题

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元