Typescript 当键类型和值类型都是并集时,如何定义键类型和值类型之间具有相关性的映射
下面的例子说明了我想要实现的目标。除了两个问题外,它几乎可以工作:Typescript 当键类型和值类型都是并集时,如何定义键类型和值类型之间具有相关性的映射,typescript,Typescript,下面的例子说明了我想要实现的目标。除了两个问题外,它几乎可以工作: 集合不显示错误代码的错误 Immer Draft类型(或任何DeepWritable实用程序类型)完全搞砸了这个把戏 基本上,在我看来,我在这里所做的并不是一件真正的事情。所以问题是:有没有其他方法可以做同样的事情 type Opaque<Type, Token = unknown> = Type & { readonly __opaque__: Token } type AnimalId = CatI
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
type不透明=type&{
只读\不透明\令牌
}
类型AnimalId=CatId | DogId
动物类型=猫|狗
CatId类型=不透明
Cat类型={
只读类:“猫”
只读id:CatId
}
DogId类型=不透明
类型狗={
只读类:“狗”
只读id:DogId
}
常数cat:cat={
种类:'猫',
id:1作为CatId,
}
警犬:警犬={
种类:'狗',
id:-1作为DogId,
}
常量动物:Map&Map&Map=newmap()
anists.set(cat.id,cat)//此处不应出现错误
设置(cat.id,dog)//想在这里看到错误吗
const test1:Cat | undefined=animals.get(Cat.id)//此处不应出现错误
const test2:Dog | undefined=animals.get(Dog.id)//此处不应出现错误
const test4:Animal | undefined=animals.get(1作为AnimalId)//此处不应出现错误
const test3=animals.get(3)//想在这里看到错误吗
交叉点地图和地图
在概念上应该足以满足您的行为需求,但在实践中,这是行不通的。函数类型的交集生成,TypeScript中重载的调用签名分别解析。它们的组合不允许单个调用调用两个调用签名(请参阅)。因此,只要使用Map&Map
,就不能调用animal.get(1作为AnimalId)
;AnimalId
既不是CatId
也不是DogId
,这是两个单独的呼叫签名所要求的
为了解决这个问题,您显然已经添加了“missing”
映射,它正是为了这个目的。所以我倾向于这样注释动物:
const animals: Map<CatId, Cat> & Map<DogId, Dog>
& ReadonlyMap<AnimalId, Animal> = new Map();
当然,这个定义可能不支持其他用例。重载确实有一些奇怪的怪癖。如果您真的在意,您可能需要手工编写自己的界面,该界面的行为与您期望的多类型Map
的行为完全相同。这是一个有趣的练习,我以前做过一个不同的案例(请参阅),老实说,这并不可怕。但我不会在这里讨论这个问题,特别是如果上面的简单交集适用于您的用例
为什么你希望这里会有错误:animals.set(cat.id,cat)//这里不应该有错误
@captain yossarian它说“没有错误”@VLAZ:)))谢谢我打赌10美元,你应该只做一个带有适当重载的包装器函数。看起来你欠jcalz 10美元了,真是太棒了!我喜欢这是一个多么详细,多么完美的结果工程。不过,我的问题还有第二部分。你能检查一下你是否也能解决这个问题吗?不幸的是,来自Immer的草稿修改器弄乱了这种类型试图实现的目标。我不知道有没有现成的地图)嗯,我不知道你是否可以修改Draft
,让它把地图的交集转换成合理的东西。没有简单的编程方法可以将交叉点分解为其组成部分。您可能需要手动将ReadonlyMap&ReadonlyMap&ReadonlyMap
转换为Map&Map&ReadonlyMap
,以获得所需的行为,而无需尝试通过像Draft
这样的通用转换器。也许其他人能知道怎么做,但我认为即使是这样,它也很脆弱。@jcalz是的,这听起来是对的。我能够手工制作这些类型,并扩展草稿类型,以便它能够了解它们。最终有点像一个搅动者,但在我看来是值得的。然而,试图将它应用到我的代码库中,我实际上遇到了最后一个问题。实际上,我希望允许使用animals.set(animal.id,animal),因为使用现成的映射,它会被禁止。问题是——你不能构造动物,使它的id与它的类型不匹配。但编译器不明白。这里还有什么可以做的吗?换句话说:const animal=猫是动物;animals.set(animal.id,animal)//此处不应出现错误
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