Recoil源码中AbstractRecoilValue<T>的contravariance及__cTag作用是什么?
你提到的Recoil类型定义如下:
export class AbstractRecoilValue<T> { __tag: [T]; __cTag: (t: T) => void; // for contravariance key: NodeKey; constructor(newKey: NodeKey); }
问题1:Recoil中contravariance(逆变)的具体含义
逆变是TypeScript类型系统中「型变规则」的一种,用来描述泛型容器的子类型关系和其泛型参数子类型关系的匹配逻辑:
- 协变:如果
A是B的子类型,那么F<A>也属于F<B>的子类型,子类型方向和泛型参数方向一致,通常用于只读数据的类型场景 - 逆变:如果
A是B的子类型,那么F<B>反而属于F<A>的子类型,子类型方向和泛型参数方向相反,通常用于可写入、可输入的类型场景
Recoil这里引入逆变规则,核心是为了保证可写状态的类型安全:比如AbstractRecoilValue<number>只允许写入数字类型的值,AbstractRecoilValue<string | number>允许写入字符串或数字。如果按照协变规则,前者可以赋值给后者的话,就会出现给原本只接受数字的状态写入字符串的类型漏洞,所以需要通过逆变规则限制这类非法赋值。
问题2:__cTag作为逆变鉴别符的工作原理
- TypeScript中函数的参数类型在开启
strictFunctionTypes配置时属于逆变位点:比如函数类型(arg: T) => void的泛型参数T是逆变的 - 原类中的
__tag: [T]是协变位点:数组的元素类型默认是协变的,如果只有这个属性,整个AbstractRecoilValue<T>的泛型T会被推导为协变,存在前面提到的类型安全漏洞 - 新增的
__cTag: (t: T) => void属性引入了逆变位点,此时TypeScript会综合协变+逆变两个位点的推导结果,将泛型T判定为不变:只有两个实例的泛型参数T完全一致时,才允许互相赋值,从类型层面杜绝了跨类型赋值导致的写入错误 - 这个属性仅用于类型推导,运行时不会实际赋值和使用,属于适配TypeScript类型系统的技巧用法
内容的提问来源于stack exchange,提问作者tondi
相关产品推荐
相关产品推荐

