这是显示我想要实现的目标的示例。除了两个问题之外,它几乎可以工作:
基本上在我看来,我在这里所做的事情并不是什么真正的事情。所以问题是:还有其他方法可以做同样的事情吗?
type Opaque<Type, Token = unknown> = Type & {
readonly __opaque__: Token
}
type AnimalId = CatId | DogId
type Animal = Cat | Dog
type CatId = Opaque<number, Cat>
type Cat = {
readonly kind: 'Cat'
readonly id: CatId
}
type DogId = Opaque<number, Dog>
type Dog = {
readonly kind: 'Dog'
readonly id: DogId
}
const cat: Cat = {
kind: 'Cat',
id: 1 as CatId,
}
const dog: Dog = {
kind: 'Dog',
id: -1 as DogId,
}
const animals: Map<CatId, Cat> & Map<DogId, Dog> & Map<AnimalId, Animal> = new Map()
animals.set(cat.id, cat) // no error should be here
animals.set(cat.id, dog) // wanna see error here
const test1: Cat | undefined = animals.get(cat.id) // no error should be here
const test2: Dog | undefined = animals.get(dog.id) // no error should be here
const test4: Animal | undefined = animals.get(1 as AnimalId) // no error should be here
const test3 = animals.get(3) // wanna see error here
Run Code Online (Sandbox Code Playgroud)
从概念上讲,交集Map<CatId, Cat> & Map<DogId, Dog>应该足以为您提供所需的行为,但实际上这是行不通的。函数类型的交集会产生重载,并且 TypeScript 中的重载调用签名会单独解析。它们不会组合在一起以允许单个调用调用两个调用签名(请参阅microsoft/TypeScript#14107)。因此,使用 just 时,您无法调用; an既不是 a也不是 a ,正如两个单独的调用签名所要求的那样。Map<CatId, Cat> & Map<DogId, Dog>animals.get(1 as AnimalId)AnimalIdCatIdDogId
为了解决这个问题,您显然添加了“missing” Map<AnimalId, Animal>。不幸的是,这太过分了。你不仅会得到理想的get()行为,还会得到不良的 set()行为。既然cat.id是一个AnimalId,而且dog是一个Animal,一个Map<AnimalId, Animal>肯定会让你打电话animals.set(cat.id, dog)。我不会深入探讨协变和逆变的迂腐细节,但一般来说,如果阅读接受事物的并集,那么写作就应该接受事物的交集,而不是并集。Map<AnimalId, Animal>因此,您想要支持的唯一方法是涉及阅读内容的方法。
对我们来说幸运的是,ReadonlyMapTypeScript 标准库中定义了一个接口,专门用于此目的。所以我倾向于animals这样注释:
const animals: Map<CatId, Cat> & Map<DogId, Dog>
& ReadonlyMap<AnimalId, Animal> = new Map();
Run Code Online (Sandbox Code Playgroud)
一旦你这样做了,你就会得到你正在寻找的行为,至少对于你的示例代码来说:
animals.set(cat.id, cat) // okay
animals.set(cat.id, dog) // error, no overload matches this call
const test1: Cat | undefined = animals.get(cat.id) // okay
const test2: Dog | undefined = animals.get(dog.id) // okay
const test4: Animal | undefined = animals.get(1 as AnimalId) // okay
const test3 = animals.get(3) // error, number is not AnimalId
Run Code Online (Sandbox Code Playgroud)
当然,可能还有该定义不支持的其他用例。重载确实有一些奇怪的怪癖。如果您真的很关心,您可能需要手写自己的界面,其行为完全符合您期望的多类型的Map行为。这是我之前针对不同案例做过的一个有趣的练习(请参阅这个问题),老实说,它并没有那么糟糕。但我不会在这里讨论这一点,特别是如果上面的更简单的交集适用于您的用例。
| 归档时间: |
|
| 查看次数: |
267 次 |
| 最近记录: |