Typescript 当键类型和值类型都是并集时,如何定义键类型和值类型之间具有相关性的映射

Typescript 当键类型和值类型都是并集时,如何定义键类型和值类型之间具有相关性的映射,typescript,Typescript,下面的例子说明了我想要实现的目标。除了两个问题外,它几乎可以工作: 集合不显示错误代码的错误 Immer Draft类型(或任何DeepWritable实用程序类型)完全搞砸了这个把戏 基本上,在我看来,我在这里所做的并不是一件真正的事情。所以问题是:有没有其他方法可以做同样的事情 type Opaque<Type, Token = unknown> = Type & { readonly __opaque__: Token } type AnimalId = CatI

下面的例子说明了我想要实现的目标。除了两个问题外,它几乎可以工作:

  • 集合不显示错误代码的错误
  • Immer Draft类型(或任何DeepWritable实用程序类型)完全搞砸了这个把戏
  • 基本上,在我看来,我在这里所做的并不是一件真正的事情。所以问题是:有没有其他方法可以做同样的事情

    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