AbstractRecoilValue<T> 上的“逆变”在 Recoil 源类型声明中是什么意思?

IT技术 reactjs typescript contravariance recoiljs
2021-05-15 10:12:11

我在 Recoil 源中偶然发现了 typescript/index.d.ts 的这一部分:

export class AbstractRecoilValue<T> {
    __tag: [T];
    __cTag: (t: T) => void; // for contravariance

    key: NodeKey;
    constructor(newKey: NodeKey);
}

这里如何__cTag作为逆变器的鉴别器?

这是整个上下文:Github

1个回答

我认为“为了逆变”可能应该说“防止协方差”,但无论如何我都会解释发生了什么。假设您有一个类型 function type F<T> = ...如果你有两种类型ABwhere B extends A,比如

interface A { a: string }
interface B extends A { b: number }

declare let a: A;
declare let b: B;
a = b; // okay
b = a; // error

什么,如果有的话,可你说之间的关系F<A>F<B>有两个有趣的案例需要考虑:

F<T>是协变的T: 每当B extends A,则F<B> extends F<A>由于扩大T将扩大F<T>和缩小T将缩小F<T>,可以说是F<T> 一起变化T它“共变”,或者是协变的。函数类型的返回类型是协变的。在 TypeScript 中,对象类型在其属性类型中被认为是协变的(尽管这在编写 时不合理,但它很有用)。 Array<T>是协变的T(尽管这在编写时不合理,但很有用)。所以[T]在协变T

F<T>是逆变的T: 每当B extends A,则F<A> extends F<B>由于加宽T会变窄而F<T>变窄T会加宽F<T>,因此可以说与F<T> 不同 T它“反变”,或者是逆变的。功能类型(与--strictFunctionTypes启用编译器选项)在他们的参数类型的逆变。所以(t: T) => void在逆变T在 TypeScript 中,对象类型的属性键类型也被认为是逆变的。

在上面的例子中,当我们说“ F<T>is covariant in T”时,我们也意味着它不是逆变的。反之亦然;当我们说“F<T>是逆变的T”时,我们也意味着它不是协变的。所以[T]在协变(但不是逆变)T,并且(t: T) => void是在逆变(但不是协变)T但我们也可以考虑这种情况:

F<T>在 T 中F<T>是双变的在 中既是协变又是逆变的T在完全健全的类型系统中,除非F<T>根本不依赖,否则不会发生这种情况T但是在 TypeScript 中,方法类型(或所有--strictFunctionTypes禁用的函数类型)在其参数类型中被认为是双变的另一种不健全但有用的情况。

最后:

F<T>在 中不变TF<T>在 中既不协变也不逆变TF<A>F<B>when之间没有简单的关系B extends A这往往是最常见的情况。协变和逆变在某种意义上是“脆弱的”,那么如果您组合协变和逆变类型,您往往会得到不变类型。这就是当您组合[T](t: T) => voidAbstractRecoilValue<T>定义中时会发生的情况_tag属性是协变的(但不是逆变)T,以及_cTag财产在逆变(但不是协变)T通过将它们放在一起,在 中AbstractRecoilValue<T>是不变的T


所以大概_cTag是添加的,以便在 中AbstractRecoilValue<T>是不变的T而不是协变的T如果您将其注释掉,您可以看到行为的差异:

declare class AbstractRecoilValue<T> {
  __tag: [T];
  // __cTag: (t: T) => void; // for contravariance
  key: NodeKey;
  constructor(newKey: NodeKey);
}

declare let arvA: AbstractRecoilValue<A>;
declare let arvB: AbstractRecoilValue<B>;

arvA = arvB; // okay
arvB = arvA; // error!

您可以看到它AbstractRecoilValue<B>可以分配给AbstractRecoilValue<A>,但反之则不然。所以AbstractRecoilValue<T>在协变T但是如果我们恢复__cTag

declare class AbstractRecoilValue<T> {
  __tag: [T];
  __cTag: (t: T) => void; // for contravariance
  key: NodeKey;
  constructor(newKey: NodeKey);
}

declare let arvA: AbstractRecoilValue<A>;
declare let arvB: AbstractRecoilValue<B>;

arvA = arvB; // error!
arvB = arvA; // error!

那么这两个赋值都是不可接受的,因此在 中AbstractRecoilValue<T>是不变的T


如果您想知道为什么对 施加这样的限制AbstractRecoilValue<T>,我无法回答,因为我不确定它的用途……不过,这似乎超出了所问问题的范围。

Playground 链接到代码