TypeScript--协变、逆变、双向协变、不可变

废话

今天深入的看了下TypeScript兼容性,陷入了迷茫,看了多个文章更迷茫了,最后只能根据自己查的资料,最后花了三小时总算搞懂了!!!!总结就是别人的文章过于文字化了或者没有具象化,给我产生了歧义,盯着 A类型变成B类型是协变还是逆变了,但是关键是想表达安全性,我手撸一下原因来讲讲

如果还没有看过官方文档(虽然很少很少内容)可以先看一下,大概有个概念

类型兼容性 · TypeScript中文网 · TypeScript——JavaScript的超集 (tslang.cn)

1 结构化和兼容

正常看别人文章都有子集、父集之类的,我觉得没必要,占用脑子注意力。

首先我们要明确的是TypeScrip是一个结构类型兼容,意思就是,只要你给我东西里面有我想要的,我就兼容你。

懂?

不懂:我要养一只狗,你却有猫和狗,那么我和你就很适合,看一下伪代码:

我:{
  狗
}

你:{
  狗,
  猫
}

我=你 // ts允许兼容,因为你有狗
你=我 // ts不允许兼容,因为我没有猫

正常代码定义:

interface Animal { name: string };
interface Dog extends Animal { myIsDog: string };

let animal: Animal = { name: '动物' };
let dog: Dog = { name: '狗', myIsDog: '我是狗' };

animal = dog;// dog包含了animal的属性, animal兼容dog
dog = animal;// 反之对animal进行dog的操作,animal不一定有,不安全 ts报错

总结:只要赋值给我的数据结构,有我要的所有的属性,就是兼容的

2 协变 Covariant

其实

animal = dog //也算协变

或者加深一点

type Co<T> = T[]; 
let adimalCo: Covariant<Animal> = []; 
let dogCo: Covariant<Dog> = []; 
adimalCo = dogCo;

协变没什么好讲的,就是看完官方文档,知道什么是兼容就行。

总结:把子类型赋值给父类型(狗(Dog)属于动物(Animal))我们定义为这个行为是安全的,与这个标准相反的基本就是逆变的意思。

3 逆变 Contravariant

然后我们看下代码

interface Animal { name: string };
interface Dog extends Animal { myIsDog: string };
interface TuDog extends Dog { myIsTuDog: string };
interface Cat extends Animal { myIsTuCat: string };
let animal: Animal = { name: '动物' };
let dog: Dog = { name: '狗', myIsDog: '我是狗' };
let tuDog: TuDog = { name: '土狗', myIsDog: '我是狗', myIsTuDog: '我是土狗' };
let cat: Cat = { name: '猫', myIsTuCat: '我是猫' };

let isDog = function (p: Dog): Dog {
  console.log(p.name)
  console.log(p.myIsDog);
  return p as TuDog
}

let isAnimal = function (p: Animal): Dog {
  console.log(p.name)
  return p as TuDog
}

isDog = isAnimal

最后isDog实际变成了isAnimal方法了,最后一句isDog = isAnimal后类型解析isDog还是原本的类型:let isDog: (p: Dog) => Dog,也就是说我调用isDog函数的时候,参数可以传Dog类型的。

可是我们限制了isAnimal方法参数是p:Animal,那么我们在方法内部的一切操作,是基于Animal有的属性去操作的,传入Dog,Dog有Animal有的所有东西,所以很安全。我们在这里相当于用参数Dog类型执行了参数Animal类型的函数。

isAnimal = isDog为什么不行 不安全呢?看代码:

isAnimal = isDog;
/** 等于 
isAnimal = function (p: Dog): Dog {
  console.log(p.name)
  console.log(p.myIsDog);
  return p as TuDog
}
**/

animal = cat;
isAnimal(animal); // 执行的时候cat.myIsDog 没有这个属性

4 Bivariant 双向协变

其实这是一个问题,Ts官方的妥协,第三点说到isAnimal = isDog 不安全,但是如果isDog里面的操作也只操作了Animal有的属性,那其实就没有问题,所以官方给出了一个配置在tsconfig.json:strictFunctionType:true,设置为true 编辑器会报错,设置为false就不会 这样就可以互相转换,具体举例子像代码这种情况:

interface Animal { name: string };
interface Dog extends Animal { myIsDog: string };
interface TuDog extends Dog { myIsTuDog: string };
interface Cat extends Animal { myIsTuCat: string };
let animal: Animal = { name: '动物' };
let dog: Dog = { name: '狗', myIsDog: '我是狗' };
let tuDog: TuDog = { name: '土狗', myIsDog: '我是狗', myIsTuDog: '我是土狗' };
let cat: Cat = { name: '猫', myIsTuCat: '我是猫' };

let animals: Animal[] = []
let addAnimal = function (p: Animal) {
  animals.push(p)
}
let addDog = function addDog(p: Dog) {
  animals.push(p)
}
addDog = addAnimal
addAnimal = addDog

我们想要知道总共有多少动物,可以把所有动物集中起来,然后再计算数组长度不就好了,其实不会有什么安全性问题

5 Invariant 不可变

很好理解既然可以双向协变(互相变),肯定有相反的,两者都不可以互相赋值的情况。

interface Animal { name: string };
interface Dog extends Animal { myIsDog: string };
interface TuDog extends Dog { myIsTuDog: string };
interface Cat extends Animal { myIsTuCat: string };
let animal: Animal = { name: '动物' };
let dog: Dog = { name: '狗', myIsDog: '我是狗' };
let tuDog: TuDog = { name: '土狗', myIsDog: '我是狗', myIsTuDog: '我是土狗' };
let cat: Cat = { name: '猫', myIsTuCat: '我是猫' };

let dogFun = function (p: Dog): Dog {
	console.log(p.myIsDog)
	return {
		name: p.name,
		myIsDog: p.myIsDog
	}
}

let tuDogFun = function (p: TuDog): TuDog {
	console.log(p.myIsDog)
	console.log(p.myIsTuDog)
	return {
		name: p.name,
		myIsDog: p.myIsDog,
		myIsTuDog: p.myIsTuDog
	}
}
// 相当于在tuDogFun方法内调用了 dog.myIsTuDog 不安全
dog = tuDog
dogFun = tuDogFun
dogFun(dog)

let tuDogFun2 = function (p: TuDog): TuDog {
	console.log(p.myIsDog)
	console.log(p.myIsTuDog)
	return {
		name: p.name,
		myIsDog: p.myIsDog,
		myIsTuDog: p.myIsTuDog
	}
}
// 如果可以把dogFun给tuDogFun 实际返回的是一只狗,但是我们tuDogFun的类型(p: TuDog) => TuDog是土狗,tuDog2的类型被推断成了TuDog,
// 执行tuDogFun2的时候其实是没有myIsTuDog属性的 -- 不安全
tuDogFun = dogFun
let tuDog2 = tuDogFun(tuDog)
tuDogFun2(tuDog2)

// 相当于把Dog当成TuDog用 ts直接就不允许了
tuDog = dog
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值