likes
comments
collection
share

TypeScript中的逆变、协变、双变和不变是个啥?

作者站长头像
站长
· 阅读数 51

大家好,我是苏先生,一名热爱钻研、乐于分享的前端工程师,跟大家分享一句我很喜欢的话:人活着,其实就是一种心态,你若觉得快乐,幸福便无处不在

github与好文

前言

TypeScript作为图灵完备的类型系统,在保持严谨的前提下,也要有一定的灵活性。本文标题所提及的内容本质上就是TypeScript在灵活性上的努力,也就是咱们常说的类型兼容性

协变

我们在javascript中用来表达父子关系的地方一般在类中,我们需要通过显示的使用extends关键字来实现

class Person{
    name:string;
}
class Sp extends Person{
    name:string;
    hobby:string[]
}

但在TypeScript中,并不需要必须显示的通过某个关键字来表达,只要结构上相似或者集合范围上存在包含关系,那我们就可以认为它是具有父子关系的:

如下,我们并没有使用显示的关键字来表达Person和Sp之间的关系,但是使用extends来比较时,它将输出你想要的结果。这说明,具有类似结构的类型之间具有父子类型关系

interface Person{
    name:string;
}
interface Sp{
    name:string;
    hobby:string[];
}

另一个用于表达父子关系的是集合类型,由于Sp所表示的范围包含Person,所以Person可以相当于是Sp的子类型

type Person = 'name'
type Sp = 'name'|'hobby'

而具有父子关系的两个类型,便具有了兼容性的特性,具体来说,我们可以使用子类型来代替父类型,而不是重新定义新类型。如下,我们可以将women强制断言为Person类型,但是反过来就不行

let women:Sp = 'name'
women = 'name' as Person

因此,我们得出结论,具有父子类型的两个类型,具有向上兼容性,这种允许子类型替代父类型使用的情况就是协变。另外,这种协变的特性不只表现在类型本身,它还具有“传染性”,如下,我们定义工具类型来分别将Person和Sp作为类型参数传递,其生成的新类型ListPerson和ListSp仍然保持原来的父子关系

type ListOf<T> = T[]
type ListPerson = ListOf<Person>
type ListSp = ListOf<Sp>

逆变

前边部分,我们说子类型代替父类型使用的情况称为协变,那我们合理推断下,让父类型去作为子类型使用的时候,是不是就是逆变了呢?此处我们暂且存疑

只不过,协变只需要关心类型本身是否结构或范围上重叠,而逆变则需要考虑运行时,当然这句话是我自己意淫的,也可能不是这样。但是在本文中作为我的主场,它必须如此

TypeScript中的逆变、协变、双变和不变是个啥?

我们称为的父子类型可能与第一直觉有偏差,实际上,子类型所表示的范围或者结构要比父类型更广。正因为如此,当我们把父类型作为子类型去使用时,就会存在类型安全问题,因为你可能会在实际运行阶段读取了某个父类型上不存在的属性或方法。如下,Person作为Sp的子类型并不能替换其使用,因为在person函数运行阶段,会读取age属性

interface Person{
    name:string
}
interface Sp{
    name:string;
    age:number;
}
function person(payload:Sp){
    console.log(payload.name,payload.age)
}

这种与协变违悖的情况就是逆变。也就是说,逆变主要作用于类型检查,而非用于替换子类型使用。并且其常发生于函数参数阶段,因为需要保证函数运行安全。同理我们也可以很容易推出,当其作为函数返回值时,此时函数运行已经结束,其将退回到协变状态

同时,逆变也同样具有“传染性”

interface Person{
    name:string
}
interface Sp{
    name:string;
    age:number;
}
function person(payload:Sp[]){
    payload.forEach()
    console.log(payload.name,payload.age)
}

双变

从名称我们也可以看出,它指的是既可以被逆变也可以被协变,但是通过前文叙述,我们知道这其实是会产生问题的。因此一般来说我们会在tsconfig.json中将strictFunctionTypes设置为true来拒绝掉这一特性

关于双变可以参考官方说明:传送门

不变

不变即两个不具备父子类型关系的类型,即不会发生协变,也不会产生逆变


如果本文对您有用,希望能得到您的点赞和收藏

订阅专栏,每周更新1-2篇类型体操,每月1-3篇vue3源码解析,等你哟😎

转载自:https://juejin.cn/post/7255239937170997307
评论
请登录