-
Notifications
You must be signed in to change notification settings - Fork 2
Description
协变和逆变的概念网上一搜一大把,就不说了,说概念一定会把人绕晕,举个例子来说就容易理解了。
假设现在有个父子关系: Parent
和 Son
interface Parent {
parentProp: string;
}
interface Son extends Parent {
sonProp: string;
}
type IsCo = Son extends Parent ? true : false; // true
变化规则为:f
⇒ 把 Parent
和 Son
变为数组:
type ParentArray = Parent[];
type SonArray = Son[];
type IsCo = SonArray extends ParentArray ? true : false; // true
因为 Son
是 Parent
的子类型,他们都变为数组后,还是满足 Son
是 Parent
的子类型,所以这个叫做协变。
此时把变化规则 f
改为函数 ⇒ 两个函数分别接收 Parent
和 Son
:
type ParentFn = (param: Parent) => void;
type SonFn = (param: Son) => void;
type IsContra = ParentFn extends SonFn ? true : false; // true
此时他们的继承关系反过来了,因为 Parent
是 Son
的子集,或者叫安全类型,所以这叫做逆变。
就有下面的公式
f(Son) ≤ f(Parent)
变化规则f
被称为:协变f(Parent) ≤ f(Son)
变化规则f
被称为:逆变
逆变的应用
逆变最常见的应用是函数重载
function fn(x: never): void;
function fn(x: string): void {}
// 等价于
function fn(x: number & string): void;
function fn(x: string): void {}
never
是 string
的子类型。
never
也可以是交叉类型的结果,如 number & string
→ never
。
题目
题目链接:UnionToIntersection
import type { Equal, Expect } from "@type-challenges/utils";
type cases = [
Expect<Equal<UnionToIntersection<"foo" | 42 | true>, "foo" & 42 & true>>,
Expect<
Equal<
UnionToIntersection<(() => "foo") | ((i: 42) => true)>,
(() => "foo") & ((i: 42) => true)
>
>
];
答案为
type UnionToIntersection<U> = (
U extends unknown ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never;
-
首先将联合类型变为
(k: Union) => void
的形式:"foo" | 42 | true -> ((k: "foo") => void) | ((k: 42) => void) | ((k: true) => void)
-
根据函数的逆变,可以得到
(k: "foo") => void <= (k: "foo" & 42 & true) => void (k: 42) => void <= (k: "foo" & 42 & true) => void (k: true) => void <= (k: "foo" & 42 & true) => void
-
最后类型推导的结果是
"foo" & 42 & true
其实看到最后的结果是有点懵的,为什么就是交叉类型呢?
把它想象成函数的重载,就好理解了
function fn(k: "foo" & 42 & true): void;
function fn(k: "foo"): void {}
// 42 和 true 同样的道理
传入什么类型可以保证 "foo"
类型安全呢?
一定是 "foo"
的子类型
那 "foo"
的子类型是什么呢?
是 never
如何得到 never
呢?
"foo" & 42 & true
TIP
要明确一个概念交叉类型 IntersectionType
是交集,联合类型 UnionType
是并集
所以 string & number & boolean
< string
< string | number | boolean
参考文章
- 大白话聊 TypeScript 中的变型
- 聊聊TypeScript类型兼容,协变、逆变、双向协变以及不变性,评论区这段话很受启发:
狗是动物,所以一群狗是一群动物,这是协变。我需要一张能刷人民币的信用卡,你给了我一张任意币种信用卡,这是符合要求的,即,人民币是任意货币的一种,任意币种信用卡是能刷人民币的信用卡,这个推论反过来了,叫逆变。