当前位置: 首页 > news >正文

TypeScript 中的协变与逆变

TypeScript 中协变与逆变

“协变(Covariance)”和“逆变(Contravariance)”是类型系统中关于**子类型关系(subtyping)**的一个核心概念,主要应用在函数类型的参数与返回值、泛型类型的继承、接口之间的赋值兼容性等场景中。

此外除了协变与逆变,还有双变和不变,不变为必须完全一致, 很好理解,而双变在严格模式中并不支持,没有了解意义。

什么是协变

用通俗的话来说协变指的是:更模糊的类型可以适配更具体的类型,例如类型中的父类(更模糊的类型)子类(更具体的类型),给父类类型指定一个子类类型是被允许的,这就是协变。

举个例子:

// 父类型
interface Animal {name: string;
}
// 子类型
interface Dog extends Animal {type: string;
}let animal: Animal = {name: '动物',
};
let dog: Dog = {name: '修狗',type: 'dog',
};animal = dog; // ✅ 将更具体的类型赋予给更广泛的类型,是允许的
dog = animal; // ❌ 将更广泛的类型断言为更具体的类型,是不允许的

我们声明了两个类型 AnimalDog,两个类型属于父子类型,当我们将 Dog 类型的数据赋予给 Animal 类型的数据时,是允许的。毕竟 Dog 属于 Animal,毕竟狗也属于动物,反过来将Animal 类型的数据赋予给 Dog 类型的数据时,是不被允许的,因为动物不一定是狗。这很符合直觉。

也就是说协变通俗来说就是:要求更模糊的数据类型但是允许赋予更具体的数据类型。

函数中的协变和逆变

先说结论,在函数中,函数的返回值是协变的,和上述所说的直接赋值一样,属于符合直觉的。但是函数的参数是逆变的

返回值的协变
返回值的协变和上面说的差不多,简单举个例子:

interface Animal {name: string;
}
interface Dog extends Animal {type: string;
}let animal: Animal = {name: '动物',
};
let dog: Dog = {name: '修狗',type: 'dog',
};type AnimalFn = () => Animal;
type DogFn = () => Dog;let animalFn: AnimalFn = () => dog; //  ✅ 要求的返回值是 Animal 但是返回 Dog 是允许的
let dogFn: DogFn = () => animal // ❌ 要求的返回值是 Dog 但是返回 Animal 是不允许的

AnimalFn 类型要求返回更模糊的 Animal 类型,但是返回更具体的 Dog 类型却是被允许的,这个是协变

参数中的逆变

前面说了协变是:要求更模糊的数据类型但是允许赋予更具体的数据类型,那么反过来逆变就是:要求更具体的数据类型但是允许赋予更模糊的数据类型。

举个例子:

interface Animal {name: string;
}
interface Dog extends Animal {type: string;
}type AnimalFn = (arg: Animal) => void;
type DogFn = (arg: Dog) => void;let animalFn: AnimalFn = (arg: Dog) => {}; // ❌ 要求的参数是 Animal 但是传入 Dog 是不允许的
let dogFn: DogFn = (arg: Animal) => {} // ✅ 要求的参数是 Dog 但是传入 Animal 是允许的

从上面代码来看,animalFnAnimalFn 类型的,要求一个 Animal 类型参数的函数,我们传入一个 Dog 类型,这属于给更模糊的类型传入一个更具体的类型,按照我们上面函数返回值协变的逻辑,应该是可以的,但是在函数参数这里是不被允许的,因为函数的参数是逆变的;
而在 dogFn 的定义中,DogFn 类型的 dogFn 事要求一个 Dog 类型的参数的,我们传入了一个 Animal 类型,这属于给更具体的类型传入更模糊的类型,这是被允许的,这就是逆变

函数参数逆变的运用

利用函数参数逆变实现件联合类型转换成交叉类型

要求:

type A = { foo: string }
type B = { bar: number }type U = A | B;// 我们想把它变成:
type I = A & B;

实现:

type A = { foo: string }
type B = { bar: number }type UnionToIntersection<T> = (T extends any ? (arg: T) => any : never) extends (arg: infer R) => any? R: never;type P = UnionToIntersection<A | B>; // P = A & B

解析:
extendsinfer 等类型推导的使用可以参考 TypeScript 这篇文章。

首先 T extends any ? (arg: T) => any : never ,我们假设传入的 T 是 A | B,那么 T extends any ? (arg: T) => any : never 推导出来的类型就是 (arg: A) => any | (arg: B) => any
然后我们利用 extends(arg: infer R) => any 去匹配 (arg: A) => any | (arg: B) => any,用 infer 将函数的参数类型提取出来命名为 R
因为 R 要兼容所有的函数的参数,而函数的参数是逆变的,也就是说实际定义的 A | B 的类型应该为更具体的类型,而 R 作为赋予 A | B 的类型应该为更模糊的类型,所以 R 需要为 A & B 才能同时作为 (arg: A) => any | (arg: B) => any 的参数类型,同时满足 AB 两种类型。
最后我们将提取到的 R 返回,从而得到类型 A & B

http://www.dtcms.com/a/316282.html

相关文章:

  • 203.移除链表元素 707.设计链表 206.反转链表
  • Python ttkbootstrap桌面应用开发示例
  • 创建一个可以edit的graph-node-test的永久登录dashboard的token
  • vue3通过按钮实现横向滚动或鼠标滚动横坐标滚动
  • 预测性维护:数字孪生如何重塑航空航天维修与寿命管理
  • Java技术栈/面试题合集(13)-网络篇
  • 李亚鹤:河南息县:淮河金沙滩开园 再添亲水休闲文旅地
  • 在Maxscript中随机化对象变换、颜色和材质
  • 拖车式变电站:灵活电力供应的“移动堡垒”,驱动全球能源转型新引擎
  • nuxt学习笔记
  • 学术论文命名:策略、方案、方法三者的区别
  • 使用Docker Desktop部署MySQL8.4.3
  • LeetCode 149:直线上最多的点数
  • 深入理解 C 语言中的拷贝函数
  • 多模态新方向|从数据融合到场景落地,解锁视觉感知新范式
  • 智能驾驶再提速!批量苏州金龙L4级自动驾驶巴士交付杭州临平区
  • 结合opencv解释图像处理中的结构元素(Structuring Element)
  • 使用PyQT创建一个简单的图形界面
  • 【面试场景题】日志去重与统计系统设计
  • 人工智能领域、图欧科技、IMYAI智能助手2025年5月更新月报
  • UGUI源码剖析(1):基础架构——UIBehaviour与Graphic的核心职责与生命周期
  • Git 中**未暂存**和**未跟踪**的区别:
  • 【深度学习-Day 41】解密循环神经网络(RNN):深入理解隐藏状态、参数共享与前向传播
  • P2161 [SHOI2009] 会场预约
  • 中山铸造加工件自动蓝光三维测量方案-中科米堆CASAIM
  • 喷砂机常见故障及排除维修解决方法有哪些?
  • 猎板深度解析:EMI 干扰 —— 电子设备的隐形 “破坏者”
  • Dot1x认证原理详解
  • 利用 Radius Resource Types 扩展平台工程能力
  • 在 QtC++ 中调用 OpenCV 实现特征检测与匹配及图像配准应用