type
status
date
slug
summary
tags
category
icon
password
本文出自《前端技术全方位深度进阶指南》系列教程-基础篇
在
TypeScript
中,类型系统的一部分是关于如何处理函数参数和返回值的类型的。这就涉及到了 逆变(contravariant) 和 协变(covariant) 的概念。在TypeScript中学习协变性和逆变性可能有些棘手,但是了解它们对于理解类型和子类型是一个很好的补充。01 子类型化
子类型化是多态性的一种形式,其中 子类型 通过某种形式的可替换性与 基本类型 相关联。 可替换性意味着基本类型的变量也可以接受子类型值。
例如,我们定义一个基类
User
和一个 Admin
(扩展 User
类)的类:由于
Admin
扩展自 User
(Admin extends User
),我们可以说Admin
是基本类型User
的子类型。Admin
(子类型)和User
(基本类型)的可替换性在于可以将Admin
类型的实例分配给User
类型的变量,例如:如何从可替代性中受益?
最大的好处之一是可以定义不依赖于细节的行为。简而言之,您可以创建接受基类型作为参数的函数,然后可以使用子类型调用该函数。
例如,编写一个将用户名记录到控制台的函数:
该函数接受的参数类型可以为
User
Admin
和任何其他基于User
子类型。这使得logUsername()
更具可重用性,并且不用关注细节。😆 工具函数
现在让我们介绍一下这个符号 A <: B —— 意思是 “A是B的子类型”。
因为
Admin
是 User
的子类型,现在你可以简写成:我们定义一个辅助类型
IsSubtypeOf<S, P>
,如果 S
是 P
的子类型,则评估为 true
,否则为 false
:IsSubtypeOf<Admin, User>
计算结果为true 因为Admin
是User
的子类型:很多类型都可以进行子类型化,包括基础类型和内置 JavaScript 类型。
例如字面量字符串
'Hello'
的类型是 string
的子类型,字面量数字 42
的类型是 number
的子类型,Map<K, V>
是Object
的子类型。02 协变(Covariant)
假设我们有一段异步代码用于获取
User
和 Admin
实例,需要处理 Promise<User>
和 Promise<Admin>
。一个有趣的问题是:如果
Admin <: User
,那么 Promise<Admin> <: Promise<User>
是否也成立?这里做个实验:当
Admin <: User
时,Promise<Admin> <: Promise<User>
也成立。这说明Promise是 协变(Covariant)类型。😁 协变(Covariant)的定义:
如果 S <: P 且 T<S> <: T<P> 那就可以说 T 是 协变类型
如果Admin是User的子类型,那么就可以预期
Promise<Admin>
是 Promise<User>
的子类型。协变在TypeScript中适用于许多类型
- A)
Promise<V>
(如上所示)
- B)
Record<K,V>
:
- C)
Map<K,V>
:
03 逆变(Contravariant)
分析以下泛型类型:
Func<Param>
创建了一个函数类型,该类型带一个类型为Param
的参数。当
Admin <: User
时,以下哪个表达式为真:Func<Admin> <: Func<User>
或者
Func<User> <: Func<Admin>
?
现在试一下:
Func<User> <: Func<Admin>
成立 - 这意味着Func<User>
是Func<Admin>
的子类型。与原始类型关系 Admin <: User
相比,子类型的方向已经发生 翻转。Func
类型的这种行为使其具有 逆变性(Contravariant) 。一般来说,函数类型相对其参数类型是逆变的。😎 逆变(Contravariant)的定义:
如果 S <: P 且 T<P> <: T<S> 那就可以说 T 是逆变类型
函数类型的子类型化方向与参数类型的子类型化方向相反。
04 函数子类型化
函数子类型化结合了协变和逆变。
如果一个函数的参数类型相对于其基本类型是逆变的(Contravariant),并且返回类型相对于其基本类型的返回类型是协变的(Covariant),那么该函数类型是其基本类型的子类型。
注:当启用 strictFunctionTypes 模式时。
换句话说,函数的子类型化要求参数类型是逆变的,而返回类型是协变的。
例如:
SubtypeFunc <: BaseFunc
因为:- A) 参数类型是逆变的(子类型化方向翻转
User :> Admin
)
- B) 返回类型是协变的(相同的子类型方向
'1' | '2' <: string
)。
了解子类型化非常有助于理解函数类型的可替换性。
例如,有一个
Admin
实例列表:接受什么类型的回调
admins.filter(...)
呢?显然,它接受带有一个 Admin
类型参数的回调:但
admins.filter(...)
是否能接受 User
类型参数的回调呢?没错
admins.filter()
接受 (admin: Admin) => boolean
基本类型,同时也接受其子类型,例如 (user: User) => boolean
。如果高阶函数接受特定类型的回调,例如
(admin: Admin) => boolean
,那么还可以提供作为特定类型的子类型的回调,例如 (user: User) => boolean
。05 结论
假设有两个类型
S
与 P
两者关系 S <: P
:1、如果
T<S> <: T<P>
(子类型方向被保持),则类型 T
是**协变的。 如 Promise<T>
即是一个协变类型。2、如果
T<P> <: T<S>
(子类型关系被翻转),则类型 T
是逆变的。函数子类型化在参数类型上是逆变的,但在返回类型上是协变的。
Loading...