【Typescript 系列】第十二节:逆变与协变

102 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

1. 引言

今天我们来介绍下什么是逆变与协变。首先在介绍之前我们先来了解下什么是父子类型,我们先来学习了解下 父子类型:

interface Animal {
  name: string
}
interface Person extends Animal {
  age: number
}
let an: Animal = {
  name: '动物'
}
let pe: Person = {
  name: '人类',
  age: 25
}

an = pe // ok
pe = an // error:Property 'age' is missing in type 'Animal' but required in type 'Person'.(2741) 

上述例子Person类型继承了Animal类型,并拥有自己单独的属性,相比于父类型更具体属性和行为更多,当我们使用赋值语句时,子类型完全可以赋值给父类型,反之则不然。我们用Person 《 Animal 说明他们的关系。

如果我们使用联合类型来举例说明呢,考考自己是否能理解清楚

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 
// ok

上述问题son是parent的子类型,parent相比于son范围更广,所以parent无法赋值给son类型,反之,可以

介绍完父子类型,我们还有了解一个什么是类型系统的类型转换,我们通过下面这个例子学习下

interface Human {
  name: string
  age: number
}
type demo = Pick<Human,'name'> // type demo = {name: string}

上述就是基础的类型转换案例,通过类型转换语法,转换目标类型

2. 含义

介绍完父子类型和类型转换,我们回归正题

什么是协变? 协变:在经过相同的类型转换之后,两者的父子关系不变。

什么是逆变? 逆变:在经过相同的类型转换之后,两者的父子关系互换。

3. 作用

协变:就是当你的类型在经过转换等处理后,还保持与其他类型在转换前的类型关系(请看例子一) 逆变:当你的类型在经过转换等处理后,转换后的类型和之前对比是互调的,通常就是函数方法的入参的位置就是逆变位(请看例子二)

4. 例子

例子一:

interface Human {
  name: string
  age: number
}
interface Animal {
  name: string
}
type CovariantFun<T> = T[]
type TooarHuman = CovariantFun<Human>
type TooarAnimal = CovariantFun<Animal>
let humanDemo:TooarHuman = [{name: 'guozi',age: 18}]
let animalDemo:TooarAnimal = [{name: 'guozi'}]
animalDemo = humanDemo // ok
humanDemo = animalDemo // error :Type 'TooarAnimal' is not assignable to type 'TooarHuman'.Property 'age' is missing in type 'Animal' but required in type 'Human'.

例子二:


interface Human {
  name: string,
  age: number
}
interface Animal {
  name: string
}

let human:Human = {
  name: 'zzz',
  age: 12
}
let animal:Animal = {
  name: 'www'
}
animal = human // ok: human是animal 的子类型
human = animal // error: Property 'age' is missing in type 'Animal' but required in type 'Human'.

type HumanFun = (arg:Human) => void
type AnimalFun = (arg:Animal) => void

let humanDemo: HumanFun = (human: Human)=>{
  console.log(human.name)
  console.log(human.age)
}
let animalDemo: AnimalFun = (animal: Animal)=>{
  console.log(human.name)
}
animalDemo = humanDemo // error: Type 'HumanFun' is not assignable to type 'AnimalFun'.
  // Types of parameters 'arg' and 'arg' are incompatible.
    // Type 'Animal' is not assignable to type 'Human'.(232
humanDemo = animalDemo // ok


由上述例子发现,类型在通过函数传参后父子关系由之前animal = human‘正确’ 变为animalDemo = humanDemo ‘错误’,这就是比较典型的“逆变”,==在上面的例子我们可以这么理解当作为函数的入参时humanDemo 接受两个入参,而animalDemo只接受一个入参,当函数只接受一个入参,你传两个,不行;如果接受两个入参,你传一个,可以==;

5. 总结

这篇文章的感悟是我学习 TS 途中遇到的一个问题查询资料并理解后所诞生的。如果有错误或疏漏欢迎大家批评指正 同时扩展下:infer 在协变和逆变的情况下是有不同现象的。